diff --git a/.env.sample-dev b/.env.sample-dev index d77245ab12..3a48d8ebb0 100644 --- a/.env.sample-dev +++ b/.env.sample-dev @@ -14,7 +14,7 @@ GEOIPUPDATE_LICENSE_KEY=xxx # List of space-separated database edition IDs. Edition IDs may # consist of letters, digits, and dashes. For example, GeoIP2-City # would download the GeoIP2 City database (GeoIP2-City). -GEOIPUPDATE_EDITION_IDS=GeoLite2-Country +GEOIPUPDATE_EDITION_IDS=GeoLite2-City # The number of hours between geoipupdate runs. If this is not set # or is set to 0, geoipupdate will run once and exit. GEOIPUPDATE_FREQUENCY=72 diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 00b13da72b..9e42b31964 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -26,7 +26,7 @@ jobs: path: .github/workflows/support-files/notifications/deny.message notification: needs: cargo-deny - runs-on: ubuntu-20.04 + runs-on: custom-runner-linux steps: - name: Check out repository code uses: actions/checkout@v2 @@ -35,18 +35,23 @@ jobs: with: name: report path: .github/workflows/support-files/notifications - - name: Keybase - Node Install + - name: install npm + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Matrix - Node Install run: npm install working-directory: .github/workflows/support-files - - name: Keybase - Send Notification + - name: Matrix - Send Notification env: NYM_NOTIFICATION_KIND: security NYM_PROJECT_NAME: "Daily security report" GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" - KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" - KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" - KEYBASE_NYM_CHANNEL: "security" + MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" + MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_AUDIT }}" + MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" + MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" + MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" uses: docker://keybaseio/client:stable-node with: args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/build-and-upload-binaries-ci.yml b/.github/workflows/build-and-upload-binaries-ci.yml new file mode 100644 index 0000000000..929913fb4f --- /dev/null +++ b/.github/workflows/build-and-upload-binaries-ci.yml @@ -0,0 +1,121 @@ +name: Build and upload binaries to CI + +on: + workflow_dispatch: + push: + paths: + - 'clients/**' + - 'common/**' + - 'contracts/**' + - 'explorer-api/**' + - 'gateway/**' + - 'integrations/**' + - 'mixnode/**' + - 'sdk/rust/nym-sdk/**' + - 'service-providers/**' + - 'nym-api/**' + - 'nym-outfox/**' + - 'tools/nym-cli/**' + - 'tools/ts-rs-cli/**' + pull_request: + paths: + - 'clients/**' + - 'common/**' + - 'contracts/**' + - 'explorer-api/**' + - 'gateway/**' + - 'integrations/**' + - 'mixnode/**' + - 'sdk/rust/nym-sdk/**' + - 'service-providers/**' + - 'nym-api/**' + - 'nym-outfox/**' + - 'tools/nym-cli/**' + - 'tools/ts-rs-cli/**' + +env: + NETWORK: mainnet + +jobs: + publish-nym: + strategy: + fail-fast: false + matrix: + platform: [ubuntu-20.04] + + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v3 + + - name: Prepare build output directory + shell: bash + env: + OUTPUT_DIR: ci-builds/${{ github.ref_name }} + run: | + rm -rf ci-builds || true + mkdir -p $OUTPUT_DIR + echo $OUTPUT_DIR + + - name: Install Dependencies (Linux) + run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + continue-on-error: true + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: Build all binaries + uses: actions-rs/cargo@v1 + with: + command: build + args: --workspace --release --all + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + override: true + components: rustfmt, clippy + + - name: Install wasm-opt + run: cargo install --version 0.110.0 wasm-opt + + - name: Build release contracts + run: make wasm + + - name: Prepare build output + shell: bash + env: + OUTPUT_DIR: ci-builds/${{ github.ref_name }} + run: | + cp target/release/nym-client $OUTPUT_DIR + cp target/release/nym-gateway $OUTPUT_DIR + cp target/release/nym-mixnode $OUTPUT_DIR + cp target/release/nym-socks5-client $OUTPUT_DIR + cp target/release/nym-api $OUTPUT_DIR + cp target/release/nym-network-requester $OUTPUT_DIR + cp target/release/nym-network-statistics $OUTPUT_DIR + cp target/release/nym-cli $OUTPUT_DIR + cp target/release/credential $OUTPUT_DIR + cp target/release/explorer-api $OUTPUT_DIR + + cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR + cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR + cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_bandwidth.wasm $OUTPUT_DIR + cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_dkg.wasm $OUTPUT_DIR + cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR + cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR + + - name: Deploy branch to CI www + continue-on-error: true + uses: easingthemes/ssh-deploy@main + env: + SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }} + ARGS: "-avzr" + SOURCE: "ci-builds/" + REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }} + REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} + TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/ + EXCLUDE: "/dist/, /node_modules/" diff --git a/.github/workflows/build-and-upload-binaries.yml b/.github/workflows/build-and-upload-binaries.yml new file mode 100644 index 0000000000..54a3fb51d1 --- /dev/null +++ b/.github/workflows/build-and-upload-binaries.yml @@ -0,0 +1,59 @@ +name: Build and upload binaries to artifact storage + +on: + workflow_dispatch: + inputs: + add_tokio_unstable: + description: 'True to add RUSTFLAGS="--cfg tokio_unstable"' + required: true + default: false + type: boolean + +env: + NETWORK: mainnet + +jobs: + publish-nym: + strategy: + fail-fast: false + matrix: + platform: [ubuntu-20.04] + + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v3 + + - name: Install Dependencies (Linux) + run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + continue-on-error: true + + - name: Sets env vars for tokio if set in manual dispatch inputs + run: | + echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV + if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: Build all binaries + uses: actions-rs/cargo@v1 + with: + command: build + args: --workspace --release + + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: nym-binaries-artifacts + path: | + target/release/nym-client + target/release/nym-gateway + target/release/nym-mixnode + target/release/nym-socks5-client + target/release/nym-api + target/release/nym-network-requester + target/release/nym-network-statistics + target/release/nym-cli + retention-days: 30 diff --git a/.github/workflows/build-ts-packages.yml b/.github/workflows/build-ts-packages.yml index 2dbf56ad7b..ba744b1a65 100644 --- a/.github/workflows/build-ts-packages.yml +++ b/.github/workflows/build-ts-packages.yml @@ -12,6 +12,7 @@ jobs: - uses: actions/checkout@v2 - name: Install rsync run: sudo apt-get install rsync + continue-on-error: true - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: @@ -42,10 +43,10 @@ jobs: REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ts-${{ env.GITHUB_REF_SLUG }}-example EXCLUDE: "/dist/, /node_modules/" - - name: Keybase - Node Install + - name: Matrix - Node Install run: npm install working-directory: .github/workflows/support-files - - name: Keybase - Send Notification + - name: Matrix - Send Notification env: NYM_NOTIFICATION_KIND: ts-packages NYM_PROJECT_NAME: "ts-packages" @@ -53,11 +54,12 @@ jobs: NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}" GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" GIT_BRANCH: "${GITHUB_REF##*/}" - KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" - KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" - KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" - KEYBASE_NYM_CHANNEL: "ci-ts-packages" IS_SUCCESS: "${{ job.status == 'success' }}" + MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" + MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}" + MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" + MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" + MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" uses: docker://keybaseio/client:stable-node with: args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad1f3d4d54..b3945372ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,21 +2,44 @@ name: Continuous integration on: push: - paths-ignore: - - 'explorer/**' + paths: + - 'clients/**' + - 'common/**' + - 'explorer-api/**' + - 'gateway/**' + - 'integrations/**' + - 'mixnode/**' + - 'sdk/rust/nym-sdk/**' + - 'service-providers/**' + - 'nym-api/**' + - 'nym-outfox/**' + - 'tools/nym-cli/**' + - 'tools/ts-rs-cli/**' pull_request: - paths-ignore: - - 'explorer/**' + paths: + - 'clients/**' + - 'common/**' + - 'explorer-api/**' + - 'gateway/**' + - 'integrations/**' + - 'mixnode/**' + - 'sdk/rust/nym-sdk/**' + - 'service-providers/**' + - 'nym-api/**' + - 'nym-outfox/**' + - 'tools/nym-cli/**' + - 'tools/ts-rs-cli/**' jobs: build: runs-on: [ self-hosted, custom-linux ] - # Enable sccache via environment variable + # Enable sccache via environment variable env: RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache steps: - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + continue-on-error: true - name: Check out repository code uses: actions/checkout@v2 @@ -41,47 +64,34 @@ jobs: command: build args: --workspace + - name: Build all examples + uses: actions-rs/cargo@v1 + with: + command: build + args: --workspace --examples + - name: Run all tests uses: actions-rs/cargo@v1 with: command: test - args: --workspace --all-features + args: --workspace - name: Run expensive tests if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master' uses: actions-rs/cargo@v1 with: command: test - args: --workspace --all-features -- --ignored + args: --workspace -- --ignored - uses: actions-rs/clippy-check@v1 name: Clippy checks + continue-on-error: true with: token: ${{ secrets.GITHUB_TOKEN }} - args: --workspace --all-features + args: --workspace - name: Run clippy uses: actions-rs/cargo@v1 with: command: clippy - args: --workspace -- -D warnings - - # COCONUT stuff - - - name: Build all binaries with coconut enabled - uses: actions-rs/cargo@v1 - with: - command: build - args: --workspace --features=coconut - - - name: Run all tests with coconut enabled - uses: actions-rs/cargo@v1 - with: - command: test - args: --workspace --features=coconut - - - name: Run clippy with coconut enabled - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all-targets --features=coconut -- -D warnings + args: --workspace --all-targets -- -D warnings diff --git a/.github/workflows/check-merge-conflicts.yml b/.github/workflows/check-merge-conflicts.yml new file mode 100644 index 0000000000..7df37e7a9c --- /dev/null +++ b/.github/workflows/check-merge-conflicts.yml @@ -0,0 +1,61 @@ +name: check-merge-conflicts + +# Check that the latest release branch merges into master and develop without +# any conflicts that git is not able to resolve + +on: + workflow_dispatch: + schedule: + - cron: '5 6 * * *' + +jobs: + get_release: + runs-on: ubuntu-latest + outputs: + output1: ${{ steps.step2.outputs.latest_release }} + steps: + - name: Check out repository code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set output variable to latest release branch + id: step2 + run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1)" >> $GITHUB_OUTPUT + + check-merge-release-into-master: + name: Check that the release branch merges into master + needs: get_release + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup git user + run: | + git config --global user.name "ci" + git config --global user.email "ci@localhost" + - name: Check merge release branch into master + run: | + ./.github/workflows/support-files/git-merge-check.sh origin/master $branch1 + env: + branch1: ${{needs.get_release.outputs.output1}} + + check-merge-release-into-develop: + name: Check that the release branch merges into develop + needs: get_release + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup git user + run: | + git config --global user.name "ci" + git config --global user.email "ci@localhost" + - name: Check merge release branch into develop + run: | + ./.github/workflows/support-files/git-merge-check.sh origin/develop $branch1 + env: + branch1: ${{needs.get_release.outputs.output1}} diff --git a/.github/workflows/ci-binary-checker.yml b/.github/workflows/ci-binary-checker.yml new file mode 100644 index 0000000000..167f6dcad9 --- /dev/null +++ b/.github/workflows/ci-binary-checker.yml @@ -0,0 +1,56 @@ +name: Run config checks on all binaries + +on: + workflow_dispatch: + release: + types: [created] + push: + paths: + - 'clients/**' + - 'common/**' + - 'contracts/**' + - 'integrations/**' + - 'mixnode/**' + - 'sdk/rust/nym-sdk/**' + - 'service-providers/**' + pull_request: + paths: + - 'clients/**' + - 'common/**' + - 'gateway/**' + - 'integrations/**' + - 'mixnode/**' + - 'sdk/rust/nym-sdk/**' + - 'service-providers/**' + +env: + NETWORK: mainnet + +jobs: + publish-nym: + strategy: + fail-fast: false + matrix: + platform: [custom-runner-linux] + + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v3 + + - name: Install Dependencies (Linux) + run: sudo apt-get update && sudo apt-get -y install jq vim libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + continue-on-error: true + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: Branch name + run: echo running on branch ${GITHUB_REF##*/} + + - name: Run tests against binaries + run: ./build_and_run.sh ${{ github.head_ref || github.ref_name }} + working-directory: tests/ + + diff --git a/.github/workflows/connect-android-build.yml b/.github/workflows/connect-android-build.yml new file mode 100644 index 0000000000..fb8f837268 --- /dev/null +++ b/.github/workflows/connect-android-build.yml @@ -0,0 +1,138 @@ +name: Nym Connect - Android APK Build + +on: + workflow_dispatch: + push: + branches: + - "release/nc-android-v[0-9].[0-9].[0-9]*" + +jobs: + build: + name: Build APK + runs-on: custom-runner-linux + env: + ANDROID_HOME: ${{ github.workspace }}/android-sdk + NDK_VERSION: 25.1.8937393 + NDK_HOME: ${{ github.workspace }}/android-sdk/ndk/25.1.8937393 + SDK_PLATFORM_VERSION: android-33 + SDK_BUILDTOOLS_VERSION: 33.0.1 + + steps: + - name: Install Dependencies (Linux) + # https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/linux/#1-system-dependencies + run: | + sudo apt-get update + sudo apt-get -y install \ + build-essential \ + unzip \ + curl \ + wget \ + libssl-dev \ + squashfs-tools \ + librsvg2-dev + + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Java + uses: actions/setup-java@v3 + with: + distribution: "temurin" + java-version: "17" + + - name: Install Android SDK manager + # https://developer.android.com/studio/command-line/sdkmanager + run: | + curl -sS https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -o cmdline-tools.zip + unzip cmdline-tools.zip + mkdir -p $ANDROID_HOME/cmdline-tools/latest + mv cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest + rm -rf cmdline-tools + + - name: Install Android S/NDK + run: | + echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses + echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \ + "platforms;$SDK_PLATFORM_VERSION" \ + "platform-tools" \ + "ndk;$NDK_VERSION" \ + "build-tools;$SDK_BUILDTOOLS_VERSION" + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + # TODO this step takes a considerable amount of time + # We could avoid to compile from source tauri-cli and use instead + # pre-compiled binary provided by the node package `@tauri-apps/cli` + # But when using the later the build fails for some reason + # so keep installing and using tauri-cli + - name: Install tauri cli + run: cargo install tauri-cli --version "^2.0.0-alpha.2" + + - name: Install rust android targets + run: | + rustup target add aarch64-linux-android \ + armv7-linux-androideabi \ + i686-linux-android \ + x86_64-linux-android + + - name: Setup Nodejs + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install yarn + run: | + npm i -g yarn + yarn --version + + - name: Build frontend code + run: | + yarn install --frozen-lockfile + yarn build + yarn workspace @nym/nym-connect-mobile webpack:prod + + - name: Build APK + working-directory: nym-connect/mobile + env: + # NODE_TAURI_CLI=${{ github.workspace }}/nym-connect/mobile/node_modules/.bin/tauri + ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }} + WRY_ANDROID_PACKAGE: net.nymtech.nym_connect + WRY_ANDROID_LIBRARY: nym_connect + # TODO build with release profile (--release), it will requires + # to sign the APK. For now build with debug profile to avoid that + # TODO build using `yarn tauri`, provide NODE_TAURI_CLI, see TODO notes above + run: cargo tauri android build --debug --apk --split-per-abi -t aarch64 + + # TODO add the version number to APK name + - name: Rename APK artifact + run: | + mkdir apk/ + mv nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build/outputs/apk/arm64/debug/app-arm64-debug.apk \ + apk/nym-connect-arm64-debug.apk + mv nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build/outputs/apk/x86_64/debug/app-x86_64-debug.apk \ + apk/nym-connect-x86_64-debug.apk + + - name: Upload APK artifact + uses: actions/upload-artifact@v3 + with: + name: nc-apk-debug + path: | + apk/nym-connect-arm64-debug.apk + apk/nym-connect-x86_64-debug.apk + + # publish: + # name: Publish APK + # needs: build + # runs-on: ubuntu-latest + # steps: + # - name: Checkout + # uses: actions/checkout@v3 + # - name: Download binary artifact + # uses: actions/download-artifact@v3 + # with: + # name: nc-apk-debug + # path: apk + # # TODO add a step to upload the APK somewhere + # - name: Publish + # uses: ??? diff --git a/.github/workflows/nym-connect.yml b/.github/workflows/connect-desktop-ci.yml similarity index 74% rename from .github/workflows/nym-connect.yml rename to .github/workflows/connect-desktop-ci.yml index 5a652e92d2..5846212222 100644 --- a/.github/workflows/nym-connect.yml +++ b/.github/workflows/connect-desktop-ci.yml @@ -1,13 +1,13 @@ -name: CI for nym-connect +name: CI for nym-connect - Desktop on: push: paths: - - 'nym-connect/**' + - 'nym-connect/desktop/**' defaults: run: - working-directory: nym-connect + working-directory: nym-connect/desktop jobs: build: @@ -16,6 +16,7 @@ jobs: - uses: actions/checkout@v2 - name: Install rsync run: sudo apt-get install rsync + continue-on-error: true - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: @@ -33,15 +34,15 @@ jobs: env: SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }} ARGS: "-rltgoDzvO --delete" - SOURCE: "nym-connect/storybook-static/" + SOURCE: "nym-connect/desktop/storybook-static/" REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }} REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/nym-connect-${{ env.GITHUB_REF_SLUG }} EXCLUDE: "/dist/, /node_modules/" - - name: Keybase - Node Install + - name: Matrix - Node Install run: npm install working-directory: .github/workflows/support-files - - name: Keybase - Send Notification + - name: Matrix - Send Notification env: NYM_NOTIFICATION_KIND: nym-connect NYM_PROJECT_NAME: "nym-connect" @@ -49,11 +50,12 @@ jobs: NYM_CI_WWW_LOCATION: "nym-connect-${{ env.GITHUB_REF_SLUG }}" GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" GIT_BRANCH: "${GITHUB_REF##*/}" - KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" - KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" - KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" - KEYBASE_NYM_CHANNEL: "ci-nym-connect" IS_SUCCESS: "${{ job.status == 'success' }}" + MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" + MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}" + MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" + MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" + MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" uses: docker://keybaseio/client:stable-node with: args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/connect-desktop.yml b/.github/workflows/connect-desktop.yml new file mode 100644 index 0000000000..e74b67a3bd --- /dev/null +++ b/.github/workflows/connect-desktop.yml @@ -0,0 +1,75 @@ +name: Nym Connect - desktop (Rust) + +on: + push: + paths: + - "nym-connect/desktop/src-tauri/**" + - "nym-connect/desktop/src-tauri/Cargo.toml" + - "clients/client-core/**" + - "clients/socks5/**" + - "common/**" + - "gateway/gateway-requests/**" + - "contracts/vesting/**" + - "nym-api/nym-api-requests/**" + pull_request: + paths: + - "nym-connect/desktop/src-tauri/**" + - "nym-connect/desktop/src-tauri/Cargo.toml" + - "clients/client-core/**" + - "clients/socks5/**" + - "common/**" + - "gateway/gateway-requests/**" + - "contracts/vesting/**" + - "nym-api/nym-api-requests/**" + +jobs: + build: + runs-on: [self-hosted, custom-linux] + env: + RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache + steps: + - name: Install Dependencies (Linux) + run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools libayatana-appindicator3-dev + continue-on-error: true + + - name: Check out repository code + uses: actions/checkout@v2 + + - name: Install rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt, clippy + + - name: Build all binaries + uses: actions-rs/cargo@v1 + with: + command: build + args: --manifest-path nym-connect/desktop/Cargo.toml --workspace + + - name: Run all tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path nym-connect/desktop/Cargo.toml --workspace + + - name: Check formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --manifest-path nym-connect/desktop/Cargo.toml --all -- --check + + - uses: actions-rs/clippy-check@v1 + name: Clippy checks + continue-on-error: true + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --manifest-path nym-connect/desktop/Cargo.toml --workspace --all-features + + - name: Run clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --manifest-path nym-connect/desktop/Cargo.toml --workspace --all-features -- -D warnings diff --git a/.github/workflows/connect-mobile.yml b/.github/workflows/connect-mobile.yml new file mode 100644 index 0000000000..05f0add6e2 --- /dev/null +++ b/.github/workflows/connect-mobile.yml @@ -0,0 +1,72 @@ +name: Nym Connect - mobile (Rust) + +on: + push: + paths: + - "nym-connect/mobile/src-tauri/**" + - "nym-connect/mobile/src-tauri/Cargo.toml" + - "!nym-connect/mobile/src-tauri/gen/**" + - "clients/client-core/**" + - "clients/socks5/**" + - "common/**" + - "gateway/gateway-requests/**" + - "contracts/vesting/**" + - "nym-api/nym-api-requests/**" + pull_request: + paths: + - "nym-connect/mobile/src-tauri/**" + - "nym-connect/mobile/src-tauri/Cargo.toml" + - "!nym-connect/mobile/src-tauri/gen/**" + - "clients/client-core/**" + - "clients/socks5/**" + - "common/**" + - "gateway/gateway-requests/**" + - "contracts/vesting/**" + - "nym-api/nym-api-requests/**" + +jobs: + build: + #runs-on: [self-hosted, custom-linux] + runs-on: ubuntu-22.04 + #env: + #RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache + #defaults: + #run: + #working-directory: nym-connect/mobile/src-tauri/ + steps: + - name: Install Dependencies (Linux) + run: | + sudo apt-get update + sudo apt-get -y install \ + libwebkit2gtk-4.1-dev \ + build-essential \ + curl \ + wget \ + libssl-dev \ + libgtk-3-dev \ + squashfs-tools \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + libsoup-3.0-dev \ + libjavascriptcoregtk-4.1-dev + + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: clippy, rustfmt + + - name: Check formatting + run: cargo fmt --manifest-path nym-connect/mobile/src-tauri/Cargo.toml -- --check + + - name: Build all binaries + run: cargo build --manifest-path nym-connect/mobile/src-tauri/Cargo.toml + + - name: Run all tests + run: cargo test --manifest-path nym-connect/mobile/src-tauri/Cargo.toml + + - name: Clippy + run: cargo clippy --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all-targets -- -D warnings diff --git a/.github/workflows/connect.yml b/.github/workflows/connect.yml deleted file mode 100644 index 292fee7482..0000000000 --- a/.github/workflows/connect.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Nym Connect (rust) - -on: - push: - paths-ignore: - - 'explorer/**' - -jobs: - build: - runs-on: [ self-hosted, custom-linux ] - env: - RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache - steps: - - name: Install Dependencies (Linux) - run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools libayatana-appindicator3-dev - - - name: Check out repository code - uses: actions/checkout@v2 - - - name: Install rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - components: rustfmt, clippy - - - name: Build all binaries - uses: actions-rs/cargo@v1 - with: - command: build - args: --manifest-path nym-connect/Cargo.toml --workspace - - - name: Run all tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path nym-connect/Cargo.toml --workspace - - - name: Check formatting - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --manifest-path nym-connect/Cargo.toml --all -- --check - - - uses: actions-rs/clippy-check@v1 - name: Clippy checks - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --manifest-path nym-connect/Cargo.toml --workspace --all-features - - - name: Run clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --manifest-path nym-connect/Cargo.toml --workspace --all-features -- -D warnings diff --git a/.github/workflows/contracts-build.yml b/.github/workflows/contracts-build.yml index 9ecc6b49d9..36c809daf6 100644 --- a/.github/workflows/contracts-build.yml +++ b/.github/workflows/contracts-build.yml @@ -6,17 +6,11 @@ on: jobs: build: - runs-on: ubuntu-20.04 + if: ${{ (startsWith(github.ref, 'refs/tags/nym-contracts-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + runs-on: [self-hosted, custom-runner-linux] steps: - uses: actions/checkout@v2 - - name: Check the release tag starts with `nym-contracts-` - if: startsWith(github.ref, 'refs/tags/nym-contracts-') == false && github.event_name != 'workflow_dispatch' - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Release tag did not start with nym-contracts-...') - - name: Install Rust stable uses: actions-rs/toolchain@v1 with: @@ -25,6 +19,9 @@ jobs: override: true components: rustfmt, clippy + - name: Install wasm-opt + run: cargo install --version 0.110.0 wasm-opt + - name: Build release contracts run: make wasm diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 6d51356a80..0a5b5bd786 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -2,11 +2,13 @@ name: Contracts on: push: - paths-ignore: - - 'explorer/**' + paths: + - 'contracts/**' + - 'common/**' pull_request: paths-ignore: - - 'explorer/**' + - 'contracts/**' + - 'common/**' jobs: matrix_prep: diff --git a/.github/workflows/network-explorer-api.yml b/.github/workflows/network-explorer-api.yml deleted file mode 100644 index 5c60038347..0000000000 --- a/.github/workflows/network-explorer-api.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: CI for Network Explorer API - -on: - workflow_dispatch: - release: - types: [created] - -env: - NETWORK: mainnet - -jobs: - publish-nym: - strategy: - fail-fast: false - matrix: - platform: [ubuntu-20.04] - - runs-on: ${{ matrix.platform }} - steps: - - uses: actions/checkout@v3 - - - name: Install Dependencies (Linux) - run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools - - - name: Check the release tag starts with `nym-explorer-api-` - if: startsWith(github.ref, 'refs/tags/nym-explorer-api-') == false && github.event_name != 'workflow_dispatch' - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Release tag did not start with nym-explorer-api-...') - - - name: Install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - - name: Build all explorer-api - uses: actions-rs/cargo@v1 - with: - command: build - args: --manifest-path explorer-api/Cargo.toml --workspace --release - - - name: Upload Artifact - uses: actions/upload-artifact@v3 - with: - name: my-artifact - path: | - target/release/explorer-api - retention-days: 30 - - - name: Upload to release based on tag name - uses: softprops/action-gh-release@v1 - if: github.event_name == 'release' - with: - files: | - target/release/explorer-api diff --git a/.github/workflows/network-explorer.yml b/.github/workflows/network-explorer.yml index 2f5b10de94..9f50548105 100644 --- a/.github/workflows/network-explorer.yml +++ b/.github/workflows/network-explorer.yml @@ -17,6 +17,7 @@ jobs: - uses: actions/checkout@v2 - name: Install rsync run: sudo apt-get install rsync + continue-on-error: true - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: @@ -56,10 +57,10 @@ jobs: REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ne-sb-${{ env.GITHUB_REF_SLUG }} EXCLUDE: "/dist/, /node_modules/" - - name: Keybase - Node Install + - name: Matrix - Node Install run: npm install working-directory: .github/workflows/support-files - - name: Keybase - Send Notification + - name: Matrix - Send Notification env: NYM_NOTIFICATION_KIND: network-explorer NYM_PROJECT_NAME: "Network Explorer" @@ -68,11 +69,12 @@ jobs: NYM_CI_WWW_LOCATION_STORYBOOK: "ne-sb-${{ env.GITHUB_REF_SLUG }}" GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" GIT_BRANCH: "${GITHUB_REF##*/}" - KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" - KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" - KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" - KEYBASE_NYM_CHANNEL: "ci-network-explorer" IS_SUCCESS: "${{ job.status == 'success' }}" + MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" + MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}" + MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" + MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" + MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" uses: docker://keybaseio/client:stable-node with: args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/nightly_build.yml b/.github/workflows/nightly_build.yml index d103b66278..64b95f6368 100644 --- a/.github/workflows/nightly_build.yml +++ b/.github/workflows/nightly_build.yml @@ -10,7 +10,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: # creates the matrix strategy from nightly_build_matrix_includes.json - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - id: set-matrix uses: JoshuaTheMiller/conditional-build-matrix@main with: @@ -25,10 +25,11 @@ jobs: steps: - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + continue-on-error: true if: matrix.os == 'ubuntu-20.04' - name: Check out repository code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install rust toolchain uses: actions-rs/toolchain@v1 @@ -38,15 +39,33 @@ jobs: override: true components: rustfmt, clippy + - name: Check formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + - name: Build all binaries uses: actions-rs/cargo@v1 with: command: build args: --workspace - - name: Reclaim some disk space (because Windows is being annoying) + - name: Reclaim some disk space uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} + if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }} + with: + command: clean + + - name: Build all examples + uses: actions-rs/cargo@v1 + with: + command: build + args: --workspace --examples + + - name: Reclaim some disk space + uses: actions-rs/cargo@v1 + if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }} with: command: clean @@ -56,9 +75,9 @@ jobs: command: test args: --workspace - - name: Reclaim some disk space (because Windows is being annoying) + - name: Reclaim some disk space uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} + if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }} with: command: clean @@ -67,25 +86,20 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --workspace --all-features -- --ignored + args: --workspace -- --ignored - - name: Check formatting + - name: Reclaim some disk space uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - - name: Reclaim some disk space (because Windows is being annoying) - uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} + if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }} with: command: clean - uses: actions-rs/clippy-check@v1 name: Clippy checks + continue-on-error: true with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features + args: --workspace - name: Run clippy uses: actions-rs/cargo@v1 @@ -100,38 +114,6 @@ jobs: with: command: clean - # COCONUT stuff - - name: Build all binaries with coconut enabled - uses: actions-rs/cargo@v1 - with: - command: build - args: --workspace --features=coconut - - - name: Reclaim some disk space (because Windows is being annoying) - uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} - with: - command: clean - - - name: Run all tests with coconut enabled - uses: actions-rs/cargo@v1 - with: - command: test - args: --workspace --features=coconut - - - name: Reclaim some disk space (because Windows is being annoying) - uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} - with: - command: clean - - - name: Run clippy with coconut enabled - uses: actions-rs/cargo@v1 - if: ${{ matrix.rust != 'nightly' }} - with: - command: clippy - args: --workspace --all-targets --features=coconut -- -D warnings - # nym-wallet (the rust part) - name: Build nym-wallet rust code uses: actions-rs/cargo@v1 @@ -160,17 +142,22 @@ jobs: notification: needs: build - runs-on: ubuntu-20.04 + runs-on: custom-runner-linux steps: - name: Collect jobs status uses: technote-space/workflow-conclusion-action@v2 - name: Check out repository code - uses: actions/checkout@v2 - - name: Keybase - Node Install + uses: actions/checkout@v3 + - name: install npm + uses: actions/setup-node@v3 + if: env.WORKFLOW_CONCLUSION == 'failure' + with: + node-version: 16 + - name: Matrix - Node Install if: env.WORKFLOW_CONCLUSION == 'failure' run: npm install working-directory: .github/workflows/support-files - - name: Keybase - Send Notification + - name: Matrix - Send Notification if: env.WORKFLOW_CONCLUSION == 'failure' env: NYM_NOTIFICATION_KIND: nightly @@ -178,11 +165,12 @@ jobs: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" GIT_BRANCH: "${GITHUB_REF##*/}" - KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" - KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" - KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" - KEYBASE_NYM_CHANNEL: "ci-nightly" IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}" + MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" + MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}" + MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" + MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" + MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" uses: docker://keybaseio/client:stable-node with: args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/nightly_build_release.yml b/.github/workflows/nightly_build_release.yml index 30e43d7f90..71b7df2bd7 100644 --- a/.github/workflows/nightly_build_release.yml +++ b/.github/workflows/nightly_build_release.yml @@ -28,7 +28,7 @@ jobs: run: git fetch --all - name: Set output variable to latest release branch id: step2 - run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+' | tail -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT + run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT build: needs: [get_release,matrix_prep] strategy: @@ -38,6 +38,7 @@ jobs: steps: - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + continue-on-error: true if: matrix.os == 'ubuntu-20.04' - name: Check out latest release branch @@ -53,15 +54,33 @@ jobs: override: true components: rustfmt, clippy + - name: Check formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + - name: Build all binaries uses: actions-rs/cargo@v1 with: command: build args: --workspace - - name: Reclaim some disk space (because Windows is being annoying) + - name: Reclaim some disk space uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} + if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }} + with: + command: clean + + - name: Build all examples + uses: actions-rs/cargo@v1 + with: + command: build + args: --workspace --examples + + - name: Reclaim some disk space + uses: actions-rs/cargo@v1 + if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }} with: command: clean @@ -71,9 +90,9 @@ jobs: command: test args: --workspace - - name: Reclaim some disk space (because Windows is being annoying) + - name: Reclaim some disk space uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} + if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }} with: command: clean @@ -82,25 +101,20 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --workspace --all-features -- --ignored + args: --workspace -- --ignored - - name: Check formatting + - name: Reclaim some disk space uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - - name: Reclaim some disk space (because Windows is being annoying) - uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} + if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }} with: command: clean - uses: actions-rs/clippy-check@v1 name: Clippy checks + continue-on-error: true with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features + args: --workspace - name: Run clippy uses: actions-rs/cargo@v1 @@ -115,38 +129,6 @@ jobs: with: command: clean - # COCONUT stuff - - name: Build all binaries with coconut enabled - uses: actions-rs/cargo@v1 - with: - command: build - args: --workspace --features=coconut - - - name: Reclaim some disk space (because Windows is being annoying) - uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} - with: - command: clean - - - name: Run all tests with coconut enabled - uses: actions-rs/cargo@v1 - with: - command: test - args: --workspace --features=coconut - - - name: Reclaim some disk space (because Windows is being annoying) - uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} - with: - command: clean - - - name: Run clippy with coconut enabled - uses: actions-rs/cargo@v1 - if: ${{ matrix.rust != 'nightly' }} - with: - command: clippy - args: --workspace --all-targets --features=coconut -- -D warnings - # nym-wallet (the rust part) - name: Build nym-wallet rust code uses: actions-rs/cargo@v1 @@ -175,17 +157,22 @@ jobs: notification: needs: [build,get_release] - runs-on: ubuntu-20.04 + runs-on: custom-runner-linux steps: - name: Collect jobs status uses: technote-space/workflow-conclusion-action@v2 - name: Check out repository code uses: actions/checkout@v3 - - name: Keybase - Node Install + - name: install npm + uses: actions/setup-node@v3 + if: env.WORKFLOW_CONCLUSION == 'failure' + with: + node-version: 16 + - name: Matrix - Node Install if: env.WORKFLOW_CONCLUSION == 'failure' run: npm install working-directory: .github/workflows/support-files - - name: Keybase - Send Notification + - name: Matrix - Send Notification if: env.WORKFLOW_CONCLUSION == 'failure' env: NYM_NOTIFICATION_KIND: nightly @@ -193,11 +180,12 @@ jobs: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" GIT_BRANCH_NAME: "${{needs.get_release.outputs.output1}}" - KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" - KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" - KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" - KEYBASE_NYM_CHANNEL: "ci-nightly-release" IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}" + MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" + MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}" + MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" + MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" + MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" uses: docker://keybaseio/client:stable-node with: args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/nightly_build_release2.yml b/.github/workflows/nightly_build_release2.yml index 6ff5368c56..ee85a1c978 100644 --- a/.github/workflows/nightly_build_release2.yml +++ b/.github/workflows/nightly_build_release2.yml @@ -28,7 +28,7 @@ jobs: run: git fetch --all - name: Set output variable to latest release branch id: step2 - run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+' | tail -n 2 | head -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT + run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 2 | head -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT build: needs: [get_release,matrix_prep] strategy: @@ -38,6 +38,7 @@ jobs: steps: - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + continue-on-error: true if: matrix.os == 'ubuntu-20.04' - name: Check out latest release branch @@ -53,6 +54,12 @@ jobs: override: true components: rustfmt, clippy + - name: Check formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + - name: Build all binaries uses: actions-rs/cargo@v1 with: @@ -65,6 +72,18 @@ jobs: with: command: clean + - name: Build all examples + uses: actions-rs/cargo@v1 + with: + command: build + args: --workspace --examples + + - name: Reclaim some disk space (because Windows is being annoying) + uses: actions-rs/cargo@v1 + if: ${{ matrix.os == 'windows-latest' }} + with: + command: clean + - name: Run all tests uses: actions-rs/cargo@v1 with: @@ -73,7 +92,7 @@ jobs: - name: Reclaim some disk space (because Windows is being annoying) uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} + if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }} with: command: clean @@ -82,13 +101,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --workspace --all-features -- --ignored - - - name: Check formatting - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + args: --workspace -- --ignored - name: Reclaim some disk space (because Windows is being annoying) uses: actions-rs/cargo@v1 @@ -98,9 +111,10 @@ jobs: - uses: actions-rs/clippy-check@v1 name: Clippy checks + continue-on-error: true with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features + args: --workspace - name: Run clippy uses: actions-rs/cargo@v1 @@ -115,38 +129,6 @@ jobs: with: command: clean - # COCONUT stuff - - name: Build all binaries with coconut enabled - uses: actions-rs/cargo@v1 - with: - command: build - args: --workspace --features=coconut - - - name: Reclaim some disk space (because Windows is being annoying) - uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} - with: - command: clean - - - name: Run all tests with coconut enabled - uses: actions-rs/cargo@v1 - with: - command: test - args: --workspace --features=coconut - - - name: Reclaim some disk space (because Windows is being annoying) - uses: actions-rs/cargo@v1 - if: ${{ matrix.os == 'windows-latest' }} - with: - command: clean - - - name: Run clippy with coconut enabled - uses: actions-rs/cargo@v1 - if: ${{ matrix.rust != 'nightly' }} - with: - command: clippy - args: --workspace --all-targets --features=coconut -- -D warnings - # nym-wallet (the rust part) - name: Build nym-wallet rust code uses: actions-rs/cargo@v1 @@ -175,17 +157,22 @@ jobs: notification: needs: [build,get_release] - runs-on: ubuntu-20.04 + runs-on: custom-runner-linux steps: - name: Collect jobs status uses: technote-space/workflow-conclusion-action@v2 - name: Check out repository code uses: actions/checkout@v3 - - name: Keybase - Node Install + - name: install npm + uses: actions/setup-node@v3 + if: env.WORKFLOW_CONCLUSION == 'failure' + with: + node-version: 16 + - name: Matrix - Node Install if: env.WORKFLOW_CONCLUSION == 'failure' run: npm install working-directory: .github/workflows/support-files - - name: Keybase - Send Notification + - name: Matrix - Send Notification if: env.WORKFLOW_CONCLUSION == 'failure' env: NYM_NOTIFICATION_KIND: nightly @@ -193,11 +180,12 @@ jobs: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" GIT_BRANCH_NAME: "${{needs.get_release.outputs.output1}}" - KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" - KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" - KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" - KEYBASE_NYM_CHANNEL: "ci-nightly-release" IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}" + MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" + MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}" + MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" + MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" + MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" uses: docker://keybaseio/client:stable-node with: args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/nym-cli-publish.yml b/.github/workflows/nym-cli-publish.yml deleted file mode 100644 index 5fd532c68b..0000000000 --- a/.github/workflows/nym-cli-publish.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Publish Nym CLI binaries - -on: - workflow_dispatch: - release: - types: [created] - -env: - NETWORK: mainnet - -jobs: - publish-nym-cli: - strategy: - fail-fast: false - matrix: - platform: [ubuntu-20.04, windows-latest, macos-latest] - - runs-on: ${{ matrix.platform }} - steps: - - uses: actions/checkout@v3 - - - name: Check the release tag starts with `nym-cli-` - if: startsWith(github.ref, 'refs/tags/nym-cli-') == false && github.event_name != 'workflow_dispatch' - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Release tag did not start with nym-cli-...') - - - name: Install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - - name: Build binary - run: make build-nym-cli - - - name: Upload Artifact - uses: actions/upload-artifact@v3 - with: - name: nym-cli-${{ matrix.platform }} - path: | - target/release/nym-cli* - retention-days: 30 - - - name: Upload to release based on tag name - uses: softprops/action-gh-release@v1 - if: github.event_name == 'release' - with: - files: | - target/release/nym-cli diff --git a/.github/workflows/nym-connect-publish-macos.yml b/.github/workflows/nym-connect-publish-macos.yml index c327fe3af5..0c9b33e416 100644 --- a/.github/workflows/nym-connect-publish-macos.yml +++ b/.github/workflows/nym-connect-publish-macos.yml @@ -1,4 +1,4 @@ -name: Publish Nym Connect (MacOS) +name: Publish Nym Connect - desktop (MacOS) on: workflow_dispatch: release: @@ -6,10 +6,11 @@ on: defaults: run: - working-directory: nym-connect + working-directory: nym-connect/desktop jobs: publish-tauri: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} strategy: fail-fast: false matrix: @@ -19,13 +20,6 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Check the release tag starts with `nym-connect-` - if: startsWith(github.ref, 'refs/tags/nym-connect-') == false && github.event_name != 'workflow_dispatch' - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Release tag did not start with nym-connect-...') - - name: Node v16 uses: actions/setup-node@v3 with: @@ -79,7 +73,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: nym-connect_1.0.0_x64.dmg - path: nym-connect/target/release/bundle/dmg/nym-connect_1.0.0_x64.dmg + path: nym-connect/desktop/target/release/bundle/dmg/nym-connect_1.0.0_x64.dmg retention-days: 30 - name: Clean up keychain @@ -92,5 +86,5 @@ jobs: if: github.event_name == 'release' with: files: | - nym-connect/target/release/bundle/dmg/*.dmg - nym-connect/target/release/bundle/macos/*.app.tar.gz* + nym-connect/desktop/target/release/bundle/dmg/*.dmg + nym-connect/desktop/target/release/bundle/macos/*.app.tar.gz* diff --git a/.github/workflows/nym-connect-publish-ubuntu.yml b/.github/workflows/nym-connect-publish-ubuntu.yml index a4eb77cae1..473371c9e0 100644 --- a/.github/workflows/nym-connect-publish-ubuntu.yml +++ b/.github/workflows/nym-connect-publish-ubuntu.yml @@ -1,4 +1,4 @@ -name: Publish Nym Connect (Ubuntu) +name: Publish Nym Connect - desktop (Ubuntu) on: workflow_dispatch: release: @@ -6,14 +6,15 @@ on: defaults: run: - working-directory: nym-connect + working-directory: nym-connect/desktop jobs: publish-tauri: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} strategy: fail-fast: false matrix: - platform: [ubuntu-20.04] + platform: [custom-runner-linux] runs-on: ${{ matrix.platform }} steps: @@ -23,17 +24,13 @@ jobs: run: > sudo apt-get update && sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev - - name: Check the release tag starts with `nym-connect-` - if: startsWith(github.ref, 'refs/tags/nym-connect-') == false && github.event_name != 'workflow_dispatch' - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Release tag did not start with nym-connect-...') + continue-on-error: true - name: Node v16 uses: actions/setup-node@v3 with: node-version: 16 + - name: Install Rust stable uses: actions-rs/toolchain@v1 with: @@ -56,7 +53,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: nym-connect.AppImage.tar.gz - path: nym-connect/target/release/bundle/appimage/nym-connect_1.0.0_amd64.AppImage + path: nym-connect/desktop/target/release/bundle/appimage/nym-connect_1.0.0_amd64.AppImage retention-days: 30 - name: Upload to release based on tag name @@ -64,5 +61,5 @@ jobs: if: github.event_name == 'release' with: files: | - nym-connect/target/release/bundle/appimage/*.AppImage - nym-connect/target/release/bundle/appimage/*.AppImage.tar.gz* + nym-connect/desktop/target/release/bundle/appimage/*.AppImage + nym-connect/desktop/target/release/bundle/appimage/*.AppImage.tar.gz* diff --git a/.github/workflows/nym-connect-publish-windows10.yml b/.github/workflows/nym-connect-publish-windows10.yml index f51349d8b4..7bd0ac1bc6 100644 --- a/.github/workflows/nym-connect-publish-windows10.yml +++ b/.github/workflows/nym-connect-publish-windows10.yml @@ -1,4 +1,4 @@ -name: Publish Nym Connect (Windows 10) +name: Publish Nym Connect - desktop (Windows 10) on: workflow_dispatch: release: @@ -6,10 +6,11 @@ on: defaults: run: - working-directory: nym-connect + working-directory: nym-connect/desktop jobs: publish-tauri: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} strategy: fail-fast: false matrix: @@ -27,13 +28,6 @@ jobs: - uses: actions/checkout@v3 - - name: Check the release tag starts with `nym-connect-` - if: startsWith(github.ref, 'refs/tags/nym-connect-') == false && github.event_name != 'workflow_dispatch' - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Release tag did not start with nym-connect-...') - - name: Import signing certificate env: WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} @@ -62,7 +56,7 @@ jobs: encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }} - name: Install app dependencies - run: yarn + run: yarn --network-timeout 100000 - name: Build and sign it env: @@ -78,7 +72,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: nym-connect_1.0.0_x64_en-US.msi - path: nym-connect/target/release/bundle/msi/nym-connect_1.0.0_x64_en-US.msi + path: nym-connect/desktop/target/release/bundle/msi/nym-connect_1.0.0_x64_en-US.msi retention-days: 30 - name: Upload to release based on tag name @@ -86,5 +80,5 @@ jobs: if: github.event_name == 'release' with: files: | - nym-connect/target/release/bundle/msi/*.msi - nym-connect/target/release/bundle/msi/*.msi.zip* + nym-connect/desktop/target/release/bundle/msi/*.msi + nym-connect/desktop/target/release/bundle/msi/*.msi.zip* diff --git a/.github/workflows/nym-release-publish.yml b/.github/workflows/nym-release-publish.yml index 979781652f..a73eabfcb3 100644 --- a/.github/workflows/nym-release-publish.yml +++ b/.github/workflows/nym-release-publish.yml @@ -16,10 +16,11 @@ env: jobs: publish-nym: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} strategy: fail-fast: false matrix: - platform: [ubuntu-20.04] + platform: [custom-runner-linux] runs-on: ${{ matrix.platform }} steps: @@ -27,13 +28,7 @@ jobs: - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools - - - name: Check the release tag starts with `nym-binaries-` - if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch' - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Release tag did not start with nym-binaries-...') + continue-on-error: true - name: Sets env vars for tokio if set in manual dispatch inputs run: | @@ -56,11 +51,12 @@ jobs: with: name: my-artifact path: | + target/release/explorer-api target/release/nym-client target/release/nym-gateway target/release/nym-mixnode target/release/nym-socks5-client - target/release/nym-validator-api + target/release/nym-api target/release/nym-network-requester target/release/nym-network-statistics target/release/nym-cli @@ -71,11 +67,12 @@ jobs: if: github.event_name == 'release' with: files: | + target/release/explorer-api target/release/nym-client target/release/nym-gateway target/release/nym-mixnode target/release/nym-socks5-client - target/release/nym-validator-api + target/release/nym-api target/release/nym-network-requester target/release/nym-network-statistics target/release/nym-cli diff --git a/.github/workflows/nym-wallet-publish-macos.yml b/.github/workflows/nym-wallet-publish-macos.yml index d7a3a88002..d3ef82759e 100644 --- a/.github/workflows/nym-wallet-publish-macos.yml +++ b/.github/workflows/nym-wallet-publish-macos.yml @@ -10,6 +10,7 @@ defaults: jobs: publish-tauri: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} strategy: fail-fast: false matrix: @@ -19,13 +20,6 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Check the release tag starts with `nym-wallet-` - if: startsWith(github.ref, 'refs/tags/nym-wallet-') == false && github.event_name != 'workflow_dispatch' - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Release tag did not start with nym-wallet-...') - - name: Node v16 uses: actions/setup-node@v3 with: diff --git a/.github/workflows/nym-wallet-publish-ubuntu.yml b/.github/workflows/nym-wallet-publish-ubuntu.yml index 04ff746851..2b5a55c37a 100644 --- a/.github/workflows/nym-wallet-publish-ubuntu.yml +++ b/.github/workflows/nym-wallet-publish-ubuntu.yml @@ -9,10 +9,11 @@ defaults: jobs: publish-tauri: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} strategy: fail-fast: false matrix: - platform: [ubuntu-20.04] + platform: [custom-runner-linux] runs-on: ${{ matrix.platform }} steps: @@ -22,17 +23,13 @@ jobs: run: > sudo apt-get update && sudo apt-get install -y webkit2gtk-4.0 - - name: Check the release tag starts with `nym-wallet-` - if: startsWith(github.ref, 'refs/tags/nym-wallet-') == false - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Release tag did not start with nym-wallet-...') + continue-on-error: true - name: Node v16 uses: actions/setup-node@v3 with: node-version: 16 + - name: Install Rust stable uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/nym-wallet-publish-windows10.yml b/.github/workflows/nym-wallet-publish-windows10.yml index cc257fcbc3..53411b7571 100644 --- a/.github/workflows/nym-wallet-publish-windows10.yml +++ b/.github/workflows/nym-wallet-publish-windows10.yml @@ -1,5 +1,6 @@ name: Publish Nym Wallet (Windows 10) on: + workflow_dispatch: release: types: [created] @@ -9,6 +10,7 @@ defaults: jobs: publish-tauri: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} strategy: fail-fast: false matrix: @@ -26,13 +28,6 @@ jobs: - uses: actions/checkout@v3 - - name: Check the release tag starts with `nym-wallet-` - if: startsWith(github.ref, 'refs/tags/nym-wallet-') == false - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Release tag did not start with nym-wallet-...') - - name: Import signing certificate env: WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} @@ -61,7 +56,7 @@ jobs: encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }} - name: Install app dependencies - run: yarn + run: yarn --network-timeout 100000 - name: Build and sign it env: @@ -73,8 +68,16 @@ jobs: TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} run: yarn build + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: nym-wallet_1.0.0_x64_en-US.msi + path: nym-wallet/target/release/bundle/msi/nym-wallet_1.*.msi + retention-days: 30 + - name: Upload to release based on tag name uses: softprops/action-gh-release@v1 + if: github.event_name == 'release' with: files: | nym-wallet/target/release/bundle/msi/*.msi diff --git a/.github/workflows/nym-wallet-storybook.yml b/.github/workflows/nym-wallet-storybook.yml index 7e4ead501f..b4b290cca2 100644 --- a/.github/workflows/nym-wallet-storybook.yml +++ b/.github/workflows/nym-wallet-storybook.yml @@ -12,6 +12,7 @@ jobs: - uses: actions/checkout@v2 - name: Install rsync run: sudo apt-get install rsync + continue-on-error: true - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: @@ -34,10 +35,10 @@ jobs: REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/wallet-${{ env.GITHUB_REF_SLUG }} EXCLUDE: "/dist/, /node_modules/" - - name: Keybase - Node Install + - name: Matrix - Node Install run: npm install working-directory: .github/workflows/support-files - - name: Keybase - Send Notification + - name: Matrix - Send Notification env: NYM_NOTIFICATION_KIND: nym-wallet NYM_PROJECT_NAME: "nym-wallet" @@ -45,11 +46,12 @@ jobs: NYM_CI_WWW_LOCATION: "wallet-${{ env.GITHUB_REF_SLUG }}" GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" GIT_BRANCH: "${GITHUB_REF##*/}" - KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" - KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" - KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" - KEYBASE_NYM_CHANNEL: "ci-nym-wallet" IS_SUCCESS: "${{ job.status == 'success' }}" + MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" + MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}" + MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" + MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" + MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" uses: docker://keybaseio/client:stable-node with: args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/nym_wallet.yml b/.github/workflows/nym_wallet.yml index 49b2381ed0..4b921ffb7a 100644 --- a/.github/workflows/nym_wallet.yml +++ b/.github/workflows/nym_wallet.yml @@ -26,6 +26,7 @@ jobs: libappindicator3-dev webkit2gtk-driver xvfb + continue-on-error: true - name: Install minimal stable uses: actions-rs/toolchain@v1 diff --git a/.github/workflows/run-api-tests.yml b/.github/workflows/run-api-tests.yml new file mode 100644 index 0000000000..1d2afec2bd --- /dev/null +++ b/.github/workflows/run-api-tests.yml @@ -0,0 +1,36 @@ +name: CI for Nym API Tests + +on: + workflow_dispatch: + push: + paths: + - "nym-api/**" + +defaults: + run: + working-directory: nym-api/tests + +jobs: + test: + name: nym-api tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install npm + run: npm install + + - name: Node v18 + uses: actions/setup-node@v3 + with: + node-version: 18.1.0 + + - name: Install yarn + run: yarn install + + - name: Run yarn + run: yarn + + - name: Run tests + run: yarn test:qa + working-directory: nym-api/tests diff --git a/.github/workflows/support-files/.env.example b/.github/workflows/support-files/.env.example index f869af7605..bbd09935d7 100644 --- a/.github/workflows/support-files/.env.example +++ b/.github/workflows/support-files/.env.example @@ -1,6 +1,9 @@ -KEYBASE_NYM_CHANNEL= -KEYBASE_NYMBOT_USERNAME= -KEYBASE_NYMBOT_PAPERKEY= +MATRIX_SERVER= +MATRIX_ROOM= +MATRIX_ROOM_OF_SHAME= +MATRIX_USER_ID= +MATRIX_TOKEN= +MATRIX_DEVICE_ID= NYM_NOTIFICATION_KIND=nightly NYM_PROJECT_NAME=Nightly Build @@ -32,4 +35,4 @@ NYM_CI_WWW_BASE=example.com # Nightly builds WORKFLOW_CONCLUSION=success -SHOW_DEBUG=true \ No newline at end of file +SHOW_DEBUG=true diff --git a/.github/workflows/support-files/.gitignore b/.github/workflows/support-files/.gitignore index 2ca1a3414b..37f81d053b 100644 --- a/.github/workflows/support-files/.gitignore +++ b/.github/workflows/support-files/.gitignore @@ -2,4 +2,6 @@ node_modules .idea # don't commit the lock file to avoid cross-platform issues -package-lock.json \ No newline at end of file +package-lock.json + +scratch \ No newline at end of file diff --git a/.github/workflows/support-files/README.md b/.github/workflows/support-files/README.md index 5e46357967..9cac90aecc 100644 --- a/.github/workflows/support-files/README.md +++ b/.github/workflows/support-files/README.md @@ -4,7 +4,7 @@ This is a collection of scripts and files to support GitHub Actions. ## Sending Notifications -These scripts send CI notifications to Keybase by creating messages from templates and env vars passed from GitHub Actions. +These scripts send CI notifications to Matrix by creating messages from templates and env vars passed from GitHub Actions. ### Adding notifications to a GitHub Action @@ -19,10 +19,11 @@ jobs: env: NYM_NOTIFICATION_KIND: "my-component" GIT_BRANCH: "${GITHUB_REF##*/}" - KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" - KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" - KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" - KEYBASE_NYM_CHANNEL: "ci-network-explorer" + MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" + MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}" + MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" + MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" + MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" IS_SUCCESS: "${{ job.status == 'success' }}" uses: docker://keybaseio/client:stable-node with: @@ -34,8 +35,8 @@ Notifications are run by adding the snippet above to a GitHub Action, and: 1. Installing node packages needed at run time 2. Set the env vars as required: - `NYM_NOTIFICATION_KIND` matches the directory in `.github/workflows/support-files/${NYM_NOTIFICATION_KIND}` to provide the templates and extra scripting in `index.js` - - Keybase credentials, channel and other env vars for the status of the build and repo -3. Replacing the default entry point shell script on the `keybaseio/client:stable-node` docker image to run `.github/workflows/support-files/notifications/entry_point.sh` + - Matrix credentials, room and other env vars for the status of the build and repo +3. Replacing the default entry point shell script on the `keybaseio/client:stable-node` docker image to run `.github/workflows/support-files/notifications/entry_point.sh` ### Running locally @@ -43,7 +44,7 @@ You will need: - Node 16 LTS - npm -Copy `.github/workflows/support-files/.env.example` to `.github/workflows/support-files/.env` and valid Keybase credentials. +Copy `.github/workflows/support-files/.env.example` to `.github/workflows/support-files/.env` and valid Matrix credentials. Then run `npm install` to get dependencies. @@ -55,4 +56,4 @@ npm install cp .env.example .env vi .env npm run dev -``` \ No newline at end of file +``` diff --git a/.github/workflows/support-files/git-merge-check.sh b/.github/workflows/support-files/git-merge-check.sh new file mode 100755 index 0000000000..30c09f352d --- /dev/null +++ b/.github/workflows/support-files/git-merge-check.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# +# Basic usage: +# ./git-merge-check origin/develop origin/release/v1.1.9 +# + +set -x + +set -o nounset +set -o pipefail + +# Start from branch ... +branch1=$1 + +# ... and try to merge in +branch2=$2 + +echo "Checking if $branch2 merges without conflicts into $branch1..." + +git checkout -q $branch1 || exit 1 + +# There are two options here as far as I see on what we should check for. Either +# +# (A) check for CONFLICT in any file except whitelist (such as .lock files) +# (B) check for "Automatic merge failed" +# +# Both of these options have drawbacks. +# +# The first (A) has the problem in that maybe we don't want to fail if the +# changes can be automatically merged (duh), but at least we are not pestered +# about constant lock file changes. +# The second (B) has the problem that it's difficult to filter out automatic +# merge fails for files we don't care about (.lock files). +# +# The ideal solution would be to check for automatic merge fails for files +# except those on a whitelist (e.g. lock files). + +# For now I choose to use (B) here, because I hope it might be less noisy + +# Alternative A +#output=$(git merge --no-commit --no-ff $branch2 | grep -v .lock) +#merge_failed=$(echo $output | grep -v "CONFLICT") +#return_code=$? + +# Alternative B +output=$(git merge --no-commit --no-ff $branch2) +merge_failed=$(echo $output | grep -v "Automatic merge failed") +return_code=$? + +# Restore + +git merge --abort +git checkout -q - + +if [ $return_code -eq 0 ]; then + echo "Merge check success" +else + echo "Merge check failed" +fi + +exit $return_code diff --git a/.github/workflows/support-files/network-explorer/templates/failure b/.github/workflows/support-files/network-explorer/templates/failure index adc2896ace..0db23cefaa 100644 --- a/.github/workflows/support-files/network-explorer/templates/failure +++ b/.github/workflows/support-files/network-explorer/templates/failure @@ -1,9 +1,14 @@ 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 > :rocket: {{ env.NYM_PROJECT_NAME }} +> > 🔴 **FAILURE** :cry: +> > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} +> > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} +> > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} +> Commit message: ``` diff --git a/.github/workflows/support-files/network-explorer/templates/success b/.github/workflows/support-files/network-explorer/templates/success index 1f380b79b9..69dd64e140 100644 --- a/.github/workflows/support-files/network-explorer/templates/success +++ b/.github/workflows/support-files/network-explorer/templates/success @@ -1,10 +1,16 @@ 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 > :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View output:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/ +> > `storybook`: https://{{ env.NYM_CI_WWW_LOCATION_STORYBOOK }}.{{ env.NYM_CI_WWW_BASE }} +> > ✅ **SUCCESS** +> > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} +> > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} +> > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} +> Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}: ``` diff --git a/.github/workflows/support-files/nightly/index.js b/.github/workflows/support-files/nightly/index.js index 3bf2516092..913aa0e7c5 100644 --- a/.github/workflows/support-files/nightly/index.js +++ b/.github/workflows/support-files/nightly/index.js @@ -151,7 +151,7 @@ async function getMessageBody(context) { return `${icon} ${job.conclusion}: ${job.name} - ${job.html_url}`; }) // and join with newlines for display in the template - .join('\n'); + .join('\n\n'); return template({ ...context, jobResults }); } diff --git a/.github/workflows/support-files/nightly/templates/failure b/.github/workflows/support-files/nightly/templates/failure index 01ad9e7a00..1a8272b6c3 100644 --- a/.github/workflows/support-files/nightly/templates/failure +++ b/.github/workflows/support-files/nightly/templates/failure @@ -1,9 +1,15 @@ 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 > :rocket: {{ env.NYM_PROJECT_NAME }} +> > 🔴 **FAILURE** :cry: +> > `when` {{ timestamp }} +> > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} +> > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} +> > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} +> {{ jobResults }} diff --git a/.github/workflows/support-files/nightly/templates/success b/.github/workflows/support-files/nightly/templates/success index 064d1ccd36..8901076548 100644 --- a/.github/workflows/support-files/nightly/templates/success +++ b/.github/workflows/support-files/nightly/templates/success @@ -1,9 +1,15 @@ 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 > :rocket: {{ env.NYM_PROJECT_NAME }} +> > ✅ **SUCCESS** +> > `when` {{ timestamp }} +> > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} +> > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} +> > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} +> -{{ jobResults }} \ No newline at end of file +{{ jobResults }} diff --git a/.github/workflows/support-files/notifications/send_message.js b/.github/workflows/support-files/notifications/send_message.js index 671e40bd84..2d0c18d6a9 100644 --- a/.github/workflows/support-files/notifications/send_message.js +++ b/.github/workflows/support-files/notifications/send_message.js @@ -1,6 +1,6 @@ require('dotenv').config(); -const Bot = require('keybase-bot'); +const { sendMatrixMessage } = require('./send_message_to_matrix'); let context = { kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect','security'], @@ -23,20 +23,27 @@ function validateContext() { 'Please set env var NYM_PROJECT_NAME with the project name for displaying in notification messages', ); } - if (!context.env.KEYBASE_NYM_CHANNEL) { - throw new Error( - 'Please set env var KEYBASE_NYM_CHANNEL with the channel name for the notification message', - ); - } - if (!context.env.KEYBASE_NYMBOT_USERNAME) { - throw new Error( - 'Username is not defined. Please set env var KEYBASE_NYMBOT_USERNAME', - ); - } - if (!context.env.KEYBASE_NYMBOT_PAPERKEY) { - throw new Error( - 'Paperkey is not defined. Please set env var KEYBASE_NYMBOT_PAPERKEY', - ); + if (context.env.MATRIX_ROOM) { + if (!context.env.MATRIX_SERVER) { + throw new Error( + 'Matrix server is not defined. Please set env var MATRIX_SERVER', + ); + } + if (!context.env.MATRIX_USER_ID) { + throw new Error( + 'Matrix user id is not defined. Please set env var MATRIX_USER_ID', + ); + } + if (!context.env.MATRIX_TOKEN) { + throw new Error( + 'Matrix token is not defined. Please set env var MATRIX_TOKEN', + ); + } + if (!context.env.MATRIX_DEVICE_ID) { + throw new Error( + 'Matrix device id is not defined. Please set env var MATRIX_DEVICE_ID', + ); + } } } @@ -61,12 +68,6 @@ function createTemplateContext() { context.kind = context.env.NYM_NOTIFICATION_KIND; - context.keybase = { - channel: context.env.KEYBASE_NYM_CHANNEL, - username: context.env.KEYBASE_NYMBOT_USERNAME, - paperkey: context.env.KEYBASE_NYMBOT_PAPERKEY, - }; - if (!context.env.GIT_BRANCH_NAME) { context.env.GIT_BRANCH_NAME = context.env.GITHUB_REF.split('/') .slice(2) @@ -76,40 +77,6 @@ function createTemplateContext() { context.status = process.env.IS_SUCCESS === 'true' ? 'success' : 'failure'; } -async function sendKeybaseMessage(messageBody) { - const bot = new Bot(); - try { - console.log( - `Initialising keybase with user "${ - context.keybase.username - }" and key: "${'*'.repeat(context.keybase.paperkey.length)}"...`, - ); - await bot.init(context.keybase.username, context.keybase.paperkey, { - verbose: false, - }); - - const channel = { - name: context.env.KEYBASE_NYMBOT_TEAM || 'nymtech_bot', - membersType: 'team', - topicName: context.keybase.channel, - topic_type: 'CHAT', - }; - const message = { - body: messageBody, - }; - - console.log(`Sending to ${channel.name}#${channel.topicName}...`); - await bot.chat.send(channel, message); - - console.log('Message sent!'); - } catch (error) { - console.error(error); - process.exitCode = -1; - } finally { - await bot.deinit(); - } -} - /** * Uses the `kind` set in the context to process the context and generate a notification message * @returns {Promise} A string notification message body @@ -146,7 +113,13 @@ async function main() { console.log(messageBody); console.log('-----------------------------------------'); } - await sendKeybaseMessage(messageBody); + if(context.env.MATRIX_ROOM) { + await sendMatrixMessage(context, messageBody, context.env.MATRIX_ROOM) + } + if(context.env.MATRIX_ROOM_OF_SHAME && context.env.IS_SUCCESS !== 'true') { + // when a job fails + await sendMatrixMessage(context, messageBody, context.env.MATRIX_ROOM_OF_SHAME) + } } // call main function and let NodeJS handle the promise diff --git a/.github/workflows/support-files/notifications/send_message_to_matrix.js b/.github/workflows/support-files/notifications/send_message_to_matrix.js new file mode 100644 index 0000000000..002af92350 --- /dev/null +++ b/.github/workflows/support-files/notifications/send_message_to_matrix.js @@ -0,0 +1,67 @@ +const sdk = require('matrix-js-sdk'); +global.Olm = require('olm'); +const { LocalStorage } = require('node-localstorage'); +const localStorage = new LocalStorage('./scratch'); +const { + LocalStorageCryptoStore, +} = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store'); +var showdown = require('showdown'); + +// hide all matrix client output +console.error = (error) => console.log('❌ error: ', error); +process.stderr.write = () => {}; +process.stdout.write = () => {}; + + +function createClient(context, room, message) { + const server = context.env.MATRIX_SERVER; + const token = context.env.MATRIX_TOKEN; + const deviceId = context.env.MATRIX_DEVICE_ID; + const userId = context.env.MATRIX_USER_ID; + + const client = sdk.createClient({ + baseUrl: server, + accessToken: token, + userId, + deviceId, + sessionStore: new sdk.WebStorageSessionStore(localStorage), + cryptoStore: new LocalStorageCryptoStore(localStorage), + }); + + client.on('sync', async function(state, prevState, res) { + if (state !== 'PREPARED') return; + client.setGlobalErrorOnUnknownDevices(false); + try { + await client.joinRoom(room); + await client.sendEvent( + room, + 'm.room.message', + { + msgtype: 'm.text', + format: 'org.matrix.custom.html', + body: message, + formatted_body: message, + }, + '', + ); + } catch (error) { + console.error('Job failed: ' + error.message); + } + client.stopClient(); + process.exit(0); + }); + + return client; +} + +async function sendMatrixMessage(contextArg, messageAsMarkdown, roomId) { + const converter = new showdown.Converter(); + const messageAsHtml = converter.makeHtml(messageAsMarkdown); + const client = createClient(contextArg, roomId, messageAsHtml); + await client.initCrypto(); + await client.startClient({ initialSyncLimit: 1 }); +} + +module.exports = { + sendMatrixMessage, +}; diff --git a/.github/workflows/support-files/nym-connect/templates/failure b/.github/workflows/support-files/nym-connect/templates/failure index adc2896ace..0db23cefaa 100644 --- a/.github/workflows/support-files/nym-connect/templates/failure +++ b/.github/workflows/support-files/nym-connect/templates/failure @@ -1,9 +1,14 @@ 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 > :rocket: {{ env.NYM_PROJECT_NAME }} +> > 🔴 **FAILURE** :cry: +> > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} +> > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} +> > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} +> Commit message: ``` diff --git a/.github/workflows/support-files/nym-connect/templates/success b/.github/workflows/support-files/nym-connect/templates/success index dbd26d69b8..4b547cb375 100644 --- a/.github/workflows/support-files/nym-connect/templates/success +++ b/.github/workflows/support-files/nym-connect/templates/success @@ -1,9 +1,14 @@ 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 > :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View storybook:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/ +> > ✅ **SUCCESS** +> > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} +> > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} +> > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} +> Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}: ``` diff --git a/.github/workflows/support-files/nym-wallet/templates/failure b/.github/workflows/support-files/nym-wallet/templates/failure index adc2896ace..0db23cefaa 100644 --- a/.github/workflows/support-files/nym-wallet/templates/failure +++ b/.github/workflows/support-files/nym-wallet/templates/failure @@ -1,9 +1,14 @@ 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 > :rocket: {{ env.NYM_PROJECT_NAME }} +> > 🔴 **FAILURE** :cry: +> > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} +> > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} +> > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} +> Commit message: ``` diff --git a/.github/workflows/support-files/nym-wallet/templates/success b/.github/workflows/support-files/nym-wallet/templates/success index 826f75e977..49f7787042 100644 --- a/.github/workflows/support-files/nym-wallet/templates/success +++ b/.github/workflows/support-files/nym-wallet/templates/success @@ -1,13 +1,18 @@ 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 > :rocket: {{ env.NYM_PROJECT_NAME }} +> > ✅ **SUCCESS** - +> > ➡️➡️➡️➡️➡️ **View output:** +> > `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }} - +> > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} +> > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} +> > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} +> Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}: ``` diff --git a/.github/workflows/support-files/package.json b/.github/workflows/support-files/package.json index b73f32259d..01470a49b9 100644 --- a/.github/workflows/support-files/package.json +++ b/.github/workflows/support-files/package.json @@ -1,6 +1,6 @@ { - "name": "send-keybase-message", - "description": "Sends a notification message with the keybase package that fails when piped into the keybase CLI", + "name": "send-matrix-message", + "description": "Sends a notification message with the matrix sdk", "version": "1.0.0", "private": true, "scripts": { @@ -10,8 +10,16 @@ "dependencies": { "dotenv": "^16.0.0", "handlebars": "^4.7.7", - "keybase-bot": "^3.6.1", - "octokit": "^1.7.1" + "matrix-js-sdk": "^9.3.0", + "node-localstorage": "^2.1.6", + "octokit": "^1.7.1", + "olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz", + "remark-emoji": "^2.2.0", + "remark-html": "^13.0.2", + "remark-parse": "^9.0.0", + "showdown": "^2.1.0", + "to-vfile": "^6.1.0", + "unified": "^9.2.2" }, "devDependencies": { "prettier": "2.3.2" diff --git a/nym-connect/dist/.gitkeep b/.github/workflows/support-files/scratch/.gitkeep similarity index 100% rename from nym-connect/dist/.gitkeep rename to .github/workflows/support-files/scratch/.gitkeep diff --git a/.github/workflows/support-files/ts-packages/templates/failure b/.github/workflows/support-files/ts-packages/templates/failure index adc2896ace..0db23cefaa 100644 --- a/.github/workflows/support-files/ts-packages/templates/failure +++ b/.github/workflows/support-files/ts-packages/templates/failure @@ -1,9 +1,14 @@ 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 > :rocket: {{ env.NYM_PROJECT_NAME }} +> > 🔴 **FAILURE** :cry: +> > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} +> > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} +> > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} +> Commit message: ``` diff --git a/.github/workflows/support-files/ts-packages/templates/success b/.github/workflows/support-files/ts-packages/templates/success index bab42a29f8..48da9fee60 100644 --- a/.github/workflows/support-files/ts-packages/templates/success +++ b/.github/workflows/support-files/ts-packages/templates/success @@ -1,14 +1,20 @@ 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 > :rocket: {{ env.NYM_PROJECT_NAME }} +> > ✅ **SUCCESS** - +> > ➡️➡️➡️➡️➡️ **View output:** +> > `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }} +> > `example`: https://{{ env.NYM_CI_WWW_LOCATION }}-example.{{ env.NYM_CI_WWW_BASE }} - +> > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} +> > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} +> > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} +> Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}: ``` diff --git a/.github/workflows/tag-and-release.yml b/.github/workflows/tag-and-release.yml new file mode 100644 index 0000000000..8b01947327 --- /dev/null +++ b/.github/workflows/tag-and-release.yml @@ -0,0 +1,46 @@ +name: tag-and-release + +on: + workflow_dispatch: + inputs: + nym_binaries_version: + description: 'Version of the nym-binaries tag' + required: false + type: string + nym_wallet_version: + description: 'Version of the nym-wallet tag' + required: false + type: string + nym_connect_version: + description: 'Version of the nym-connect tag' + required: false + type: string + +jobs: + tag-components: + uses: nymtech/reusable-workflows/.github/workflows/tag-components.yml@master + with: + nym_binaries_version: ${{ input.nym_binaries_version }} + nym_wallet_version: ${{ input.nym_wallet_version }} + nym_connect_version: ${{ input.nym_connect_version }} + + create-nym-binaries-release: + if: ${{ input.nym_binaries_version }} + uses: nymtech/reusable-workflows/.github/workflows/create-binaries-release.yml@master + with: + version: ${{ input.nym_binaries_version }} + needs: ["tag-components"] + + create-nym-wallet-release: + if: ${{ input.nym_wallet_version }} + uses: nymtech/reusable-workflows/.github/workflows/create-wallet-release.yml@master + with: + version: ${{ input.nym_wallet_version }} + needs: ["tag-components"] + + create-nym-connect-release: + if: ${{ input.nym_connect_version }} + uses: nymtech/reusable-workflows/.github/workflows/create-connect-release.yml@master + with: + version: ${{ input.nym_connect_version }} + needs: ["tag-components"] diff --git a/.github/workflows/typescript-lint.yml b/.github/workflows/typescript-lint.yml new file mode 100644 index 0000000000..b3bd15b6e6 --- /dev/null +++ b/.github/workflows/typescript-lint.yml @@ -0,0 +1,64 @@ +name: CI for linting Typescript + +on: + push: + paths: + - 'ts-packages/**' + - 'sdk/typescript/**' + - 'nym-connect/desktop/src/**' + - 'nym-connect/desktop/package.json' + - 'nym-connect/mobile/src/**' + - 'nym-connect/mobile/package.json' + - 'nym-wallet/src/**' + - 'nym-wallet/package.json' + pull_request: + paths: + - 'ts-packages/**' + - 'sdk/typescript/**' + - 'nym-connect/desktop/src/**' + - 'nym-connect/desktop/package.json' + - 'nym-connect/mobile/src/**' + - 'nym-connect/mobile/package.json' + - 'nym-wallet/src/**' + - 'nym-wallet/package.json' + +jobs: + build: + runs-on: custom-runner-linux + steps: + - uses: actions/checkout@v2 + - name: Install rsync + run: sudo apt-get install rsync + continue-on-error: true + - uses: rlespinasse/github-slug-action@v3.x + - uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Setup yarn + run: npm install -g yarn + - name: Install + run: yarn + - name: Build packages + run: yarn build + - name: Lint + run: yarn lint && yarn tsc + - name: Matrix - Node Install + run: npm install + working-directory: .github/workflows/support-files + - name: Matrix - Send Notification + env: + NYM_NOTIFICATION_KIND: ts-packages + NYM_PROJECT_NAME: "ts-packages" + NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}" + NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}" + GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" + GIT_BRANCH: "${GITHUB_REF##*/}" + IS_SUCCESS: "${{ job.status == 'success' }}" + MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}" + MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}" + MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}" + MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}" + MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}" + uses: docker://keybaseio/client:stable-node + with: + args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/.github/workflows/update-versions-and-changelog.yaml b/.github/workflows/update-versions-and-changelog.yaml new file mode 100644 index 0000000000..8d98393108 --- /dev/null +++ b/.github/workflows/update-versions-and-changelog.yaml @@ -0,0 +1,95 @@ +name: update-versions-and-changelog + +on: + workflow_dispatch: + inputs: + release_version: + description: "Release version, usually the milestone title" + required: true + type: string + milestone_id: + description: "Milestone ID, check the URL when you're on the specific milestone page" + required: true + type: string + +env: + CI_BOT_AUTHOR: "Nym bot" + CI_BOT_EMAIL: "nym-bot@users.noreply.github.com" + +jobs: + update-versions: + runs-on: ubuntu-22.04 + steps: + - name: checkout-source + uses: actions/checkout@v3 + with: + ref: "release/${{ inputs.release_version }}" + path: "nym" + + - name: checkout-ci-tools-repo + uses: actions/checkout@v3 + with: + repository: "nymtech/ci-tools" + ref: "master" + path: "ci-tools" + token: "${{ secrets.ACCESS_TOKEN_PRIVATE_REPOS }}" + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: install-version-bumper + run: "cargo install --path ." + working-directory: "ci-tools/version-bumper" + + - name: run-version-bumper + run: "version-bumper bump binaries --nym-repo-directory nym" + + - name: push-changes-to-branch + run: | + git config --global user.name "${{ env.CI_BOT_AUTHOR }}" + git config --global user.email "${{ env.CI_BOT_EMAIL }}" + git checkout -b release/${{ inputs.release_version }}-preparation + git commit -am "chore: version bump in preparation for release" + git push -u origin release/${{ inputs.release_version }}-preparation + working-directory: "nym" + + update-changelog: + runs-on: ubuntu-22.04 + needs: [update-versions] + steps: + - name: checkout-source + uses: actions/checkout@v3 + with: + ref: "release/${{ inputs.release_version }}" + path: "nym" + + - name: checkout-ci-tools-repo + uses: actions/checkout@v3 + with: + repository: "nymtech/ci-tools" + ref: "master" + path: "ci-tools" + token: "${{ secrets.ACCESS_TOKEN_PRIVATE_REPOS }}" + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: install-changelog-updater + run: "cargo install --path ." + working-directory: "ci-tools/changelog-updater" + + - name: run-changelog-updater + run: "changelog-updater mix ${{ inputs.milestone_id }} release/${{ inputs.release_version }}" + + - name: push-changes-to-branch + run: | + git config --global user.name "${{ env.CI_BOT_AUTHOR }}" + git config --global user.email "${{ env.CI_BOT_EMAIL }}" + git checkout release/${{ inputs.release_version }}-preparation + git commit -am "chore: update changelog preparation for release" + git push + working-directory: "nym" diff --git a/.github/workflows/wallet.yml b/.github/workflows/wallet.yml index 44e593d9cb..896b3103c0 100644 --- a/.github/workflows/wallet.yml +++ b/.github/workflows/wallet.yml @@ -2,11 +2,17 @@ name: Nym Wallet (rust) on: push: - paths-ignore: - - 'explorer/**' + paths: + - 'nym-wallet/**' + - 'common/**' + - 'contracts/vesting/**' + - 'nym-api/nym-api-requests/**' pull_request: - paths-ignore: - - 'explorer/**' + paths: + - 'nym-wallet/**' + - 'common/**' + - 'contracts/vesting/**' + - 'nym-api/nym-api-requests/**' jobs: build: @@ -16,6 +22,7 @@ jobs: steps: - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools + continue-on-error: true - name: Check out repository code uses: actions/checkout@v2 @@ -48,6 +55,7 @@ jobs: - uses: actions-rs/clippy-check@v1 name: Clippy checks + continue-on-error: true with: token: ${{ secrets.GITHUB_TOKEN }} args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features diff --git a/.github/workflows/wasm_client_build.yml b/.github/workflows/wasm_client_build.yml index e1da3b04b0..e71120a346 100644 --- a/.github/workflows/wasm_client_build.yml +++ b/.github/workflows/wasm_client_build.yml @@ -2,8 +2,13 @@ name: Wasm Client on: pull_request: - paths-ignore: - - 'explorer/**' + paths: + - 'clients/webassembly/**' + - 'clients/client-core/**' + - 'common/**' + - 'contracts/**' + - 'gateway/gateway-requests/**' + - 'nym-api/nym-api-requests/**' jobs: wasm: @@ -24,16 +29,6 @@ jobs: command: build args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown - - uses: actions-rs/cargo@v1 - with: - command: build - args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --features=coconut - - - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path clients/webassembly/Cargo.toml - - uses: actions-rs/cargo@v1 with: command: fmt diff --git a/.gitignore b/.gitignore index b8df49d1a7..82f4286297 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,6 @@ validator-config validator-api-config.toml dist storybook-static -envs/qwerty.env \ No newline at end of file +envs/qwerty.env +.parcel-cache +**/.DS_Store diff --git a/.wellknown/wallet/validators.json b/.wellknown/wallet/validators.json index f4987aa3dc..ff849e16d1 100644 --- a/.wellknown/wallet/validators.json +++ b/.wellknown/wallet/validators.json @@ -1,6 +1,8 @@ { - "mainnet":[{ - "nymd_url":"https://rpc.nyx.nodes.guru/", - "api_url":"https://api.nyx.nodes.guru/" - }] -} + "mainnet": [ + { + "nyxd_url": "https://rpc.nyx.nodes.guru/", + "api_url": "https://api.nyx.nodes.guru/" + } + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a9f160260d..dbc5cfaefb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,200 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [v1.1.13] (2023-03-15) + +- NE - instead of throwing a "Mixnode/Gateway not found" error for blacklisted nodes due to bad performance, show their history but tag them as "Having poor performance" ([#2979]) +- NE - Upgrade Sandbox and make below changes: ([#2332]) +- Explorer - Updates ([#3168]) +- Fix contracts and nym-api audit findings ([#3026]) +- Website v2 - deploy infrastructure for strapi and CI ([#2213]) +- add blockstream green to sp list ([#3180]) +- mock-nym-api: fix .storybook lint error ([#3178]) +- Validating new interval config parameters to prevent division by zero ([#3153]) + +[#2979]: https://github.com/nymtech/nym/issues/2979 +[#2332]: https://github.com/nymtech/nym/issues/2332 +[#3168]: https://github.com/nymtech/nym/issues/3168 +[#3026]: https://github.com/nymtech/nym/issues/3026 +[#2213]: https://github.com/nymtech/nym/issues/2213 +[#3180]: https://github.com/nymtech/nym/pull/3180 +[#3178]: https://github.com/nymtech/nym/pull/3178 +[#3153]: https://github.com/nymtech/nym/pull/3153 + +## [v1.1.12] (2023-03-07) + +- Fix generated docs for mixnet and vesting contract on docs.rs ([#3093]) +- Introduce a way of injecting topology into the client ([#3044]) +- Update mixnet TypeScript client methods #1 ([#2783]) +- Update tooltips for routing and average score ([#3133]) +- update selected service provider description style ([#3128]) + +[#3093]: https://github.com/nymtech/nym/issues/3093 +[#3044]: https://github.com/nymtech/nym/issues/3044 +[#2783]: https://github.com/nymtech/nym/issues/2783 +[#3133]: https://github.com/nymtech/nym/pull/3133 +[#3128]: https://github.com/nymtech/nym/pull/3128 + +## [v1.1.11] (2023-02-28) + +- Fix empty dealer set loop ([#3105]) +- The nym-api db.sqlite is broken when trying to run against it it in `enabled-credentials-mode true` there is an ordering issue with migrations when using the credential binary to purchase bandwidth ([#3100]) +- Feature/latency based gateway selection ([#3081]) +- Fix the credential binary to handle transactions to sleep when in non-inProgress epochs ([#3057]) +- Publish mixnet contract to crates.io ([#1919]) +- Publish vesting contract to crates.io ([#1920]) +- Feature/update checker to use master ([#3097]) +- Feature/improve binary checks ([#3094]) + +[#3105]: https://github.com/nymtech/nym/issues/3105 +[#3100]: https://github.com/nymtech/nym/issues/3100 +[#3081]: https://github.com/nymtech/nym/pull/3081 +[#3057]: https://github.com/nymtech/nym/issues/3057 +[#1919]: https://github.com/nymtech/nym/issues/1919 +[#1920]: https://github.com/nymtech/nym/issues/1920 +[#3097]: https://github.com/nymtech/nym/pull/3097 +[#3094]: https://github.com/nymtech/nym/pull/3094 + +## [v1.1.10] (2023-02-21) + +- Verloc listener causing mixnode unexpected shutdown ([#3038]) +- rust-sdk - update API following implementation experience with the network-requester ([#3001]) +- Prevent coconut deposits in incompatible states ([#2991]) +- Support unavailable signer within threshold ([#2987]) +- Implement DKG re-sharing ([#2935]) +- contracts: add nym prefix to mixnet and vesting contract packages ([#2855]) +- Introduce common interface for all service providers to allow obtaining information such as whether they're online, what binary version they're running, etc. ([#2758]) +- Add client functionality to nym-network-requester ([#1900]) +- nym-api: uptime rework ([#3053]) +- ci: update typescript-lint.yml ([#3035]) +- contracts: add nym prefix to mixnet and vesting contract packages ([#2855]) + +[#3038]: https://github.com/nymtech/nym/issues/3038 +[#3001]: https://github.com/nymtech/nym/issues/3001 +[#2991]: https://github.com/nymtech/nym/issues/2991 +[#2987]: https://github.com/nymtech/nym/issues/2987 +[#2935]: https://github.com/nymtech/nym/issues/2935 +[#2855]: https://github.com/nymtech/nym/pull/2855 +[#2758]: https://github.com/nymtech/nym/issues/2758 +[#1900]: https://github.com/nymtech/nym/issues/1900 +[#3053]: https://github.com/nymtech/nym/pull/3053 +[#3035]: https://github.com/nymtech/nym/pull/3035 +[#2855]: https://github.com/nymtech/nym/pull/2855 + +## [v1.1.9] (2023-02-07) + +### Added + +- Remove Coconut feature flag ([#2793]) +- Separate `nym-api` endpoints with values of "total-supply" and "circulating-supply" in `nym` ([#2964]) + +### Changed + +- native-client: is now capable of listening for requests on sockets different than `127.0.0.1` ([#2912]). This can be specified via `--host` flag during `init` or `run`. Alternatively a custom `host` can be set in `config.toml` file under `socket` section. +- mixnode, gateway: fix unexpected shutdown on corrupted connection ([#2963]) + +[#2793]: https://github.com/nymtech/nym/issues/2793 +[#2912]: https://github.com/nymtech/nym/issues/2912 +[#2964]: https://github.com/nymtech/nym/issues/2964 +[#2963]: https://github.com/nymtech/nym/issues/3017 + +## [v1.1.8] (2023-01-31) + +### Added + +- Rust SDK - Support SURBS (anonymous send + storage) ([#2754]) +- dkg rerun from scratch and dkg-specific epochs ([#2810]) +- Rename `'initial_supply'` field to `'total_supply'` in the circulating supply endpoint ([#2931]) +- Circulating supply api endpoint (read the note inside before testing/deploying) ([#1902]) + +### Changed + +- nym-api: an `--id` flag is now always explicitly required ([#2873]) + +[#2754]: https://github.com/nymtech/nym/issues/2754 +[#2810]: https://github.com/nymtech/nym/issues/2810 +[#2931]: https://github.com/nymtech/nym/issues/2931 +[#1902]: https://github.com/nymtech/nym/issues/1902 +[#2873]: https://github.com/nymtech/nym/issues/2873 + + +## [v1.1.7] (2023-01-24) + +### Added + +- Gateways now shut down gracefully ([#2019]). +- Rust SDK - Initial version for nym-client ([#2669]). +- Introduce vesting contract query for addresses of all vesting accounts (required for the circulating supply calculation) ([#2778]). +- Add threshold value to the contract storage ([#1893]) + +### Changed + +- Refactor vesting account storage (and in particular, ACCOUNTS saving) ([#2795]). +- Move from manual advancing DKG state to an automatic process ([#2670]). + +### Fixed + +- Gateways now shut down gracefully ([#2019]). + +[#2019]: https://github.com/nymtech/nym/issues/2019 +[#2669]: https://github.com/nymtech/nym/issues/2669 +[#2795]: https://github.com/nymtech/nym/issues/2795 +[#2778]: https://github.com/nymtech/nym/issues/2778 +[#2670]: https://github.com/nymtech/nym/issues/2670 +[#1893]: https://github.com/nymtech/nym/issues/1893 + +## [v1.1.6] (2023-01-17) + +### Added + +- nym-sdk: added initial version of a Rust client sdk +- nym-api: added `/circulating-supply` endpoint ([#2814]) +- nym-api: add endpoint listing detailed gateway info by @octol in https://github.com/nymtech/nym/pull/2833 + +### Changed + +- streamline override_config functions -> there's a lot of duplicate if statements everywhere ([#2774]) +- clean-up nym-api startup arguments/flags to use clap 3 and its macro-derived arguments ([#2772]) +- renamed all references to validator_api to nym_api +- renamed all references to nymd to nyxd ([#2696]) +- all-binaries: standarised argument names (note: old names should still be accepted) ([#2762] + +### Fixed + +- nym-api: should now correctly use `rewarding.enabled` config flag ([#2753]) + +[#2696]: https://github.com/nymtech/nym/pull/2696 +[#2753]: https://github.com/nymtech/nym/pull/2753 +[#2762]: https://github.com/nymtech/nym/pull/2762 +[#2814]: https://github.com/nymtech/nym/pull/2814 +[#2772]: https://github.com/nymtech/nym/pull/2772 +[#2774]: https://github.com/nymtech/nym/pull/2774 + +## [v1.1.5] (2023-01-10) + +### Added + +- socks5: send status message for service ready, and network-requester error response in https://github.com/nymtech/nym/pull/2715 + +### Changed + +- all-binaries: improved error logging in https://github.com/nymtech/nym/pull/2686 +- native client: bring shutdown logic up to the same level as socks5-client in https://github.com/nymtech/nym/pull/2695 +- nym-api, coconut-dkg contract: automatic, time-based dkg epoch state advancement in https://github.com/nymtech/nym/pull/2670 +- DKG resharing unit test by @neacsu in https://github.com/nymtech/nym/pull/2668 +- Renaming validator-api to nym-api by @futurechimp in https://github.com/nymtech/nym/pull/1863 +- Modify wasm specific make targets by @neacsu in https://github.com/nymtech/nym/pull/2693 +- client: create websocket handler builder by @octol in https://github.com/nymtech/nym/pull/2700 +- Outfox and Lion by @durch in https://github.com/nymtech/nym/pull/2730 +- Feature/multi surb transmission lanes by @jstuczyn in https://github.com/nymtech/nym/pull/2723 + ## [v1.1.4] (2022-12-20) -This release adds multiple Single Use Reply Blocks (SURBs) to allow arbitrarily-sized anonymized replies. -At the moment this is turned off by default, but available for use by application developers. -We will need to wait for network-requesters to upgrade to this new release, after which multi-SURB anonymization will become the default setting for the SOCKS proxy clients. +This release adds multiple Single Use Reply Blocks (SURBs) to allow arbitrarily-sized anonymized replies. +At the moment this is turned off by default, but available for use by application developers. +We will need to wait for network-requesters to upgrade to this new release, after which multi-SURB anonymization will become the default setting for the SOCKS proxy clients. -The release also include some additional work for distributed key generation in the Coconut signing authority nodes. +The release also include some additional work for distributed key generation in the Coconut signing authority nodes. ### Changed @@ -25,7 +212,7 @@ The release also include some additional work for distributed key generation in ### Changed - validator-api: can recover from shutdown during DKG process ([#1872]) -- clients: deduplicate gateway inititialization, part of work towards a rust-sdk +- clients: deduplicate gateway initialization, part of work towards a rust-sdk - clients: keep all transmission lanes going at all times by making priority probabilistic - clients: ability to use multi-reply SURBs to send arbitrarily long messages fully anonymously whilst requesting additional reply blocks whenever they're about to run out ([#1796], [#1801], [#1804], [#1835], [#1858], [#1883])) @@ -33,7 +220,7 @@ The release also include some additional work for distributed key generation in - network-requester: fix bug where websocket connection disconnect resulted in success error code - clients: fix a few panics handling the gateway-client -- mixnode, gateway, validator-api: Use mainnet values as defaults for URLs and mixnet contract ([#1884]) +- mixnode, gateway, validator-api: Use mainnet values as defaults for URLs and mixnet contract ([#1884]) - socks5: fixed bug where connections sometimes where closed too early - clients: improve message logging when received message fails to get reconstructed ([#1803]) @@ -53,7 +240,7 @@ The release also include some additional work for distributed key generation in - gateway: Renamed flag from `enabled/disabled_credentials_mode` to `only-coconut-credentials` - "Family" feature for node families + layers -- Initial coconut functionality including credentials and distributed key generation +- Initial coconut functionality including credentials and distributed key generation ## [v1.1.1](https://github.com/nymtech/nym/tree/v1.1.1) (2022-11-29) @@ -89,7 +276,6 @@ The release also include some additional work for distributed key generation in [#1786]: https://github.com/nymtech/nym/pull/1786 [#1805]: https://github.com/nymtech/nym/pull/1805 - ## [v1.1.0](https://github.com/nymtech/nym/tree/v1.1.0) (2022-11-09) ### Added @@ -101,7 +287,7 @@ The release also include some additional work for distributed key generation in - native-client/socks5-client/wasm-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671]) - network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611]) - validator-api: add `interval_operating_cost` and `profit_margin_percent` to compute reward estimation endpoint -- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558]) +- validator-client: added `query_contract_smart` and `query_contract_raw` on `NyxdClient` ([#1558]) - wasm-client: uses updated wasm-compatible `client-core` so that it's now capable of packet retransmission, cover traffic and poisson delay (among other things!) ([#1673]) ### Fixed @@ -116,7 +302,7 @@ The release also include some additional work for distributed key generation in - gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669]) - moved `Percent` struct to `contracts-common`, change affects explorer-api - socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591]) -- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681]) +- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681]) - validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541]) - wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585]) - validator-api: changes to internal SQL schema due to the mixnet contract revamp ([#1472]) @@ -144,7 +330,6 @@ The release also include some additional work for distributed key generation in [#1724]: https://github.com/nymtech/nym/pull/1724 [#1725]: https://github.com/nymtech/nym/pull/1725 - ## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2) ### Added @@ -197,9 +382,8 @@ The release also include some additional work for distributed key generation in - All binaries and cosmwasm blobs are configured at runtime now; binaries are configured using environment variables or .env files and contracts keep the configuration parameters in storage ([#1463]) - gateway, network-statistics: include gateway id in the sent statistical data ([#1478]) - network explorer: tweak how active set probability is shown ([#1503]) -- validator-api: rewarder set update fails without panicking on possible nymd queries ([#1520]) -- network-requester, socks5 client (nym-connect): send and receive respectively a message error to be displayed about filter check failure ([#1576]) - +- validator-api: rewarder set update fails without panicking on possible nyxd queries ([#1520]) +- network-requester, socks5 client (nym-connect): send and receive respectively a message error to be displayed about filter check failure ([#1576]) [#1249]: https://github.com/nymtech/nym/pull/1249 [#1256]: https://github.com/nymtech/nym/pull/1256 @@ -293,9 +477,9 @@ The release also include some additional work for distributed key generation in - Bump minimist from 1.2.5 to 1.2.6 in /clients/tauri-client [\#1163](https://github.com/nymtech/nym/pull/1163) ([dependabot[bot]](https://github.com/apps/dependabot)) - Bump minimist from 1.2.5 to 1.2.6 in /clients/webassembly/js-example [\#1162](https://github.com/nymtech/nym/pull/1162) ([dependabot[bot]](https://github.com/apps/dependabot)) - Bump minimist from 1.2.5 to 1.2.6 in /clients/native/examples/js-examples/websocket [\#1160](https://github.com/nymtech/nym/pull/1160) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Bump minimist from 1.2.5 to 1.2.6 in /docker/typescript\_client/upload\_contract [\#1159](https://github.com/nymtech/nym/pull/1159) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Bump minimist from 1.2.5 to 1.2.6 in /docker/typescript_client/upload_contract [\#1159](https://github.com/nymtech/nym/pull/1159) ([dependabot[bot]](https://github.com/apps/dependabot)) - Feature/vesting full [\#1158](https://github.com/nymtech/nym/pull/1158) ([fmtabbara](https://github.com/fmtabbara)) -- get\_current\_epoch tauri [\#1156](https://github.com/nymtech/nym/pull/1156) ([durch](https://github.com/durch)) +- get_current_epoch tauri [\#1156](https://github.com/nymtech/nym/pull/1156) ([durch](https://github.com/durch)) - Cleanup [\#1155](https://github.com/nymtech/nym/pull/1155) ([durch](https://github.com/durch)) - Feature flag reward payments [\#1154](https://github.com/nymtech/nym/pull/1154) ([durch](https://github.com/durch)) - Add Query endpoints for calculating rewards [\#1152](https://github.com/nymtech/nym/pull/1152) ([durch](https://github.com/durch)) @@ -304,7 +488,7 @@ The release also include some additional work for distributed key generation in - wallet: use Urls rather than Strings for validator urls [\#1148](https://github.com/nymtech/nym/pull/1148) ([octol](https://github.com/octol)) - Change accumulated reward to Option, migrate delegations [\#1147](https://github.com/nymtech/nym/pull/1147) ([durch](https://github.com/durch)) - wallet: fetch validators url remotely if available [\#1146](https://github.com/nymtech/nym/pull/1146) ([octol](https://github.com/octol)) -- Fix delegated\_free calculation [\#1145](https://github.com/nymtech/nym/pull/1145) ([durch](https://github.com/durch)) +- Fix delegated_free calculation [\#1145](https://github.com/nymtech/nym/pull/1145) ([durch](https://github.com/durch)) - Update Nym wallet dependencies to use `ts-packages` [\#1144](https://github.com/nymtech/nym/pull/1144) ([mmsinclair](https://github.com/mmsinclair)) - wallet: try validators one by one if available [\#1143](https://github.com/nymtech/nym/pull/1143) ([octol](https://github.com/octol)) - Update Network Explorer Packages and add mix node identity key copy [\#1142](https://github.com/nymtech/nym/pull/1142) ([mmsinclair](https://github.com/mmsinclair)) @@ -344,14 +528,13 @@ The release also include some additional work for distributed key generation in - feature/pedersen-commitments [\#1048](https://github.com/nymtech/nym/pull/1048) ([danielementary](https://github.com/danielementary)) - Feature/reuse init owner [\#970](https://github.com/nymtech/nym/pull/970) ([neacsu](https://github.com/neacsu)) - ## [v0.12.1](https://github.com/nymtech/nym/tree/v0.12.1) (2021-12-23) [Full Changelog](https://github.com/nymtech/nym/compare/v0.12.0...v0.12.1) **Implemented enhancements:** -- Add version check to binaries [\#967](https://github.com/nymtech/nym/issues/967) +- Add version check to binaries [\#967](https://github.com/nymtech/nym/issues/967) **Fixed bugs:** @@ -381,7 +564,7 @@ The release also include some additional work for distributed key generation in - Bugfix/remove mixnode bonding overwrite [\#917](https://github.com/nymtech/nym/pull/917) ([jstuczyn](https://github.com/jstuczyn)) - Fixes crash condition in validator API when calculating last day uptime [\#909](https://github.com/nymtech/nym/pull/909) ([jstuczyn](https://github.com/jstuczyn)) - Bugfix/monitor initial values wait [\#907](https://github.com/nymtech/nym/pull/907) ([jstuczyn](https://github.com/jstuczyn)) -- Bug fix: Network Explorer: Add freegeoip API key and split out tasks for country distributions [\#806](https://github.com/nymtech/nym/pull/806) ([mmsinclair](https://github.com/mmsinclair)) +- Bug fix: Network Explorer: Add freegeoip API key and split out tasks for country distributions [\#806](https://github.com/nymtech/nym/pull/806) ([mmsinclair](https://github.com/mmsinclair)) - Explorer API: port test now split out address resolution and add units tests [\#755](https://github.com/nymtech/nym/pull/755) ([mmsinclair](https://github.com/mmsinclair)) **Closed issues:** @@ -396,7 +579,7 @@ The release also include some additional work for distributed key generation in - help!!! [\#712](https://github.com/nymtech/nym/issues/712) - UX feature request: show all delegated nodes in wallet [\#711](https://github.com/nymtech/nym/issues/711) - UX feature request: add current balance on wallet pages [\#710](https://github.com/nymtech/nym/issues/710) -- got sign issue from bot [\#709](https://github.com/nymtech/nym/issues/709) +- got sign issue from bot [\#709](https://github.com/nymtech/nym/issues/709) - As a wallet user, I would like to be able to log out of the wallet [\#706](https://github.com/nymtech/nym/issues/706) - As a wallet user, I would like to have a "receive" page where I can see my own wallet address [\#705](https://github.com/nymtech/nym/issues/705) - Update native client/socks client/mixnode/gateway `upgrade` command [\#689](https://github.com/nymtech/nym/issues/689) @@ -406,7 +589,7 @@ The release also include some additional work for distributed key generation in - nym-socks5-client crash after opening Keybase team "Browse all channels" [\#494](https://github.com/nymtech/nym/issues/494) - Mixed Content problem [\#400](https://github.com/nymtech/nym/issues/400) - Gateway disk quota [\#137](https://github.com/nymtech/nym/issues/137) -- Simplify message encapsulation with regards to topology [\#127](https://github.com/nymtech/nym/issues/127) +- Simplify message encapsulation with regards to topology [\#127](https://github.com/nymtech/nym/issues/127) - Create constants for cli argument names [\#115](https://github.com/nymtech/nym/issues/115) - Using Blake3 as a hash function [\#103](https://github.com/nymtech/nym/issues/103) - Validator should decide which layer a node is in [\#86](https://github.com/nymtech/nym/issues/86) @@ -462,10 +645,10 @@ The release also include some additional work for distributed key generation in - Feature/pre cosmrs updates [\#935](https://github.com/nymtech/nym/pull/935) ([jstuczyn](https://github.com/jstuczyn)) - Feature/client on behalf [\#934](https://github.com/nymtech/nym/pull/934) ([neacsu](https://github.com/neacsu)) - Webpack wallet prod configuration [\#933](https://github.com/nymtech/nym/pull/933) ([tommyv1987](https://github.com/tommyv1987)) -- Adding tx\_hash to wallet response [\#932](https://github.com/nymtech/nym/pull/932) ([futurechimp](https://github.com/futurechimp)) +- Adding tx_hash to wallet response [\#932](https://github.com/nymtech/nym/pull/932) ([futurechimp](https://github.com/futurechimp)) - Release/1.0.0 pre1 [\#931](https://github.com/nymtech/nym/pull/931) ([durch](https://github.com/durch)) - Feature/identity verification [\#930](https://github.com/nymtech/nym/pull/930) ([jstuczyn](https://github.com/jstuczyn)) -- Move cleaned up smart contracts to main code repo [\#929](https://github.com/nymtech/nym/pull/929) ([mfahampshire](https://github.com/mfahampshire)) +- Move cleaned up smart contracts to main code repo [\#929](https://github.com/nymtech/nym/pull/929) ([mfahampshire](https://github.com/mfahampshire)) - Feature/mixnet contract further adjustments [\#928](https://github.com/nymtech/nym/pull/928) ([jstuczyn](https://github.com/jstuczyn)) - typo copy change for nodemap [\#926](https://github.com/nymtech/nym/pull/926) ([Aid19801](https://github.com/Aid19801)) - Feature/UI enhancements for Desktop Wallet [\#925](https://github.com/nymtech/nym/pull/925) ([fmtabbara](https://github.com/fmtabbara)) @@ -478,7 +661,7 @@ The release also include some additional work for distributed key generation in - Feature/faucet page react [\#911](https://github.com/nymtech/nym/pull/911) ([fmtabbara](https://github.com/fmtabbara)) - Feature/mixnet contract refactor [\#910](https://github.com/nymtech/nym/pull/910) ([futurechimp](https://github.com/futurechimp)) - Update README.md [\#905](https://github.com/nymtech/nym/pull/905) ([tommyv1987](https://github.com/tommyv1987)) -- BUG: Bond cell denom [\#904](https://github.com/nymtech/nym/pull/904) ([Aid19801](https://github.com/Aid19801)) +- BUG: Bond cell denom [\#904](https://github.com/nymtech/nym/pull/904) ([Aid19801](https://github.com/Aid19801)) - Explorer UI tests missing data-testid [\#903](https://github.com/nymtech/nym/pull/903) ([tommyv1987](https://github.com/tommyv1987)) - Fix up Nym-Wallet README.md [\#899](https://github.com/nymtech/nym/pull/899) ([tommyv1987](https://github.com/tommyv1987)) - Feature/batch delegator rewarding [\#898](https://github.com/nymtech/nym/pull/898) ([jstuczyn](https://github.com/jstuczyn)) @@ -496,7 +679,7 @@ The release also include some additional work for distributed key generation in - Reverted gateway registration handshake to its 0.11.0 version [\#882](https://github.com/nymtech/nym/pull/882) ([jstuczyn](https://github.com/jstuczyn)) - Network Explorer [\#881](https://github.com/nymtech/nym/pull/881) ([mmsinclair](https://github.com/mmsinclair)) - Feature/rewarding interval updates [\#880](https://github.com/nymtech/nym/pull/880) ([jstuczyn](https://github.com/jstuczyn)) -- Put client\_address and id in the correct order [\#875](https://github.com/nymtech/nym/pull/875) ([neacsu](https://github.com/neacsu)) +- Put client_address and id in the correct order [\#875](https://github.com/nymtech/nym/pull/875) ([neacsu](https://github.com/neacsu)) - remove gateway selection on delegation and undelegation pages [\#873](https://github.com/nymtech/nym/pull/873) ([fmtabbara](https://github.com/fmtabbara)) - Set MSRV on all binaries to 1.56 [\#872](https://github.com/nymtech/nym/pull/872) ([jstuczyn](https://github.com/jstuczyn)) - add native window items \(copy/paste\) via tauri [\#871](https://github.com/nymtech/nym/pull/871) ([fmtabbara](https://github.com/fmtabbara)) @@ -512,7 +695,7 @@ The release also include some additional work for distributed key generation in - Overflow checks in release [\#846](https://github.com/nymtech/nym/pull/846) ([jstuczyn](https://github.com/jstuczyn)) - fix delegate success overflow [\#842](https://github.com/nymtech/nym/pull/842) ([fmtabbara](https://github.com/fmtabbara)) - Feature NYM wallet webdriverio test [\#841](https://github.com/nymtech/nym/pull/841) ([tommyv1987](https://github.com/tommyv1987)) -- Update nym\_wallet.yml [\#840](https://github.com/nymtech/nym/pull/840) ([tommyv1987](https://github.com/tommyv1987)) +- Update nym_wallet.yml [\#840](https://github.com/nymtech/nym/pull/840) ([tommyv1987](https://github.com/tommyv1987)) - Feature/vouchers [\#837](https://github.com/nymtech/nym/pull/837) ([aniampio](https://github.com/aniampio)) - Apply readable ids to elements on Nym Wallet [\#836](https://github.com/nymtech/nym/pull/836) ([tommyv1987](https://github.com/tommyv1987)) - Feature/removal of monitor good nodes [\#833](https://github.com/nymtech/nym/pull/833) ([jstuczyn](https://github.com/jstuczyn)) @@ -536,8 +719,8 @@ The release also include some additional work for distributed key generation in - Created getters for AccountData [\#787](https://github.com/nymtech/nym/pull/787) ([jstuczyn](https://github.com/jstuczyn)) - Feature/migrate hidden delegations [\#786](https://github.com/nymtech/nym/pull/786) ([neacsu](https://github.com/neacsu)) - Feature/persistent gateway storage [\#784](https://github.com/nymtech/nym/pull/784) ([jstuczyn](https://github.com/jstuczyn)) -- Replaced unwrap\_or\_else with unwrap\_or\_default [\#780](https://github.com/nymtech/nym/pull/780) ([jstuczyn](https://github.com/jstuczyn)) -- Add block\_height method to Delegation [\#778](https://github.com/nymtech/nym/pull/778) ([durch](https://github.com/durch)) +- Replaced unwrap_or_else with unwrap_or_default [\#780](https://github.com/nymtech/nym/pull/780) ([jstuczyn](https://github.com/jstuczyn)) +- Add block_height method to Delegation [\#778](https://github.com/nymtech/nym/pull/778) ([durch](https://github.com/durch)) - Make fee helpers public [\#777](https://github.com/nymtech/nym/pull/777) ([durch](https://github.com/durch)) - re-enable bonding [\#776](https://github.com/nymtech/nym/pull/776) ([fmtabbara](https://github.com/fmtabbara)) - Explorer-api: add API resource to show the delegations for each mix node [\#774](https://github.com/nymtech/nym/pull/774) ([mmsinclair](https://github.com/mmsinclair)) @@ -546,14 +729,14 @@ The release also include some additional work for distributed key generation in - Adding deps for building the Tauri wallet under Ubuntu [\#770](https://github.com/nymtech/nym/pull/770) ([futurechimp](https://github.com/futurechimp)) - remove alert [\#767](https://github.com/nymtech/nym/pull/767) ([fmtabbara](https://github.com/fmtabbara)) - Feature/consumable bandwidth [\#766](https://github.com/nymtech/nym/pull/766) ([neacsu](https://github.com/neacsu)) -- Update coconut-rs and use hash\_to\_scalar from there [\#765](https://github.com/nymtech/nym/pull/765) ([neacsu](https://github.com/neacsu)) +- Update coconut-rs and use hash_to_scalar from there [\#765](https://github.com/nymtech/nym/pull/765) ([neacsu](https://github.com/neacsu)) - Feature/active sets [\#764](https://github.com/nymtech/nym/pull/764) ([jstuczyn](https://github.com/jstuczyn)) - add app alert banner [\#762](https://github.com/nymtech/nym/pull/762) ([fmtabbara](https://github.com/fmtabbara)) - Updated cosmos-sdk [\#761](https://github.com/nymtech/nym/pull/761) ([jstuczyn](https://github.com/jstuczyn)) - Feature/bond blockstamp [\#760](https://github.com/nymtech/nym/pull/760) ([neacsu](https://github.com/neacsu)) - Feature/revert migration code [\#759](https://github.com/nymtech/nym/pull/759) ([neacsu](https://github.com/neacsu)) - Bump next from 11.1.0 to 11.1.1 in /wallet-web [\#758](https://github.com/nymtech/nym/pull/758) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Add block\_height in the Delegation structure as well [\#757](https://github.com/nymtech/nym/pull/757) ([neacsu](https://github.com/neacsu)) +- Add block_height in the Delegation structure as well [\#757](https://github.com/nymtech/nym/pull/757) ([neacsu](https://github.com/neacsu)) - Feature/add blockstamp [\#756](https://github.com/nymtech/nym/pull/756) ([neacsu](https://github.com/neacsu)) - NetworkMonitorBuilder - starting the monitor after rocket has launched [\#754](https://github.com/nymtech/nym/pull/754) ([jstuczyn](https://github.com/jstuczyn)) - Enabled validators api argument [\#753](https://github.com/nymtech/nym/pull/753) ([jstuczyn](https://github.com/jstuczyn)) @@ -565,21 +748,21 @@ The release also include some additional work for distributed key generation in - Feature/more reliable uptime calculation [\#747](https://github.com/nymtech/nym/pull/747) ([jstuczyn](https://github.com/jstuczyn)) - Update template toml key [\#746](https://github.com/nymtech/nym/pull/746) ([neacsu](https://github.com/neacsu)) - Feature/cred after handshake [\#745](https://github.com/nymtech/nym/pull/745) ([neacsu](https://github.com/neacsu)) -- Reinstate the POST method blind\_sign [\#744](https://github.com/nymtech/nym/pull/744) ([neacsu](https://github.com/neacsu)) +- Reinstate the POST method blind_sign [\#744](https://github.com/nymtech/nym/pull/744) ([neacsu](https://github.com/neacsu)) - explorer-api: add pending field to port check response [\#742](https://github.com/nymtech/nym/pull/742) ([mmsinclair](https://github.com/mmsinclair)) - Feature/use delegation rates [\#741](https://github.com/nymtech/nym/pull/741) ([neacsu](https://github.com/neacsu)) - Feature/copy to clipboard [\#740](https://github.com/nymtech/nym/pull/740) ([fmtabbara](https://github.com/fmtabbara)) - Feature/update wallet with stake rates [\#739](https://github.com/nymtech/nym/pull/739) ([neacsu](https://github.com/neacsu)) - Add stake reward rates and bump version of client [\#738](https://github.com/nymtech/nym/pull/738) ([neacsu](https://github.com/neacsu)) - Bump next from 10.1.3 to 11.1.0 in /wallet-web [\#737](https://github.com/nymtech/nym/pull/737) ([dependabot[bot]](https://github.com/apps/dependabot)) -- Feature/nymd client integration [\#736](https://github.com/nymtech/nym/pull/736) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/nyxd client integration [\#736](https://github.com/nymtech/nym/pull/736) ([jstuczyn](https://github.com/jstuczyn)) - Bug/fix parking lot on wasm [\#735](https://github.com/nymtech/nym/pull/735) ([neacsu](https://github.com/neacsu)) - Explorer API: add new HTTP resource to decorate mix nodes with geoip locations [\#734](https://github.com/nymtech/nym/pull/734) ([mmsinclair](https://github.com/mmsinclair)) -- Feature/completing nymd client api [\#732](https://github.com/nymtech/nym/pull/732) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/completing nyxd client api [\#732](https://github.com/nymtech/nym/pull/732) ([jstuczyn](https://github.com/jstuczyn)) - Explorer API - add port check and node description/stats proxy [\#731](https://github.com/nymtech/nym/pull/731) ([mmsinclair](https://github.com/mmsinclair)) -- Feature/nymd client fee handling [\#730](https://github.com/nymtech/nym/pull/730) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/nyxd client fee handling [\#730](https://github.com/nymtech/nym/pull/730) ([jstuczyn](https://github.com/jstuczyn)) - Update DelegationCheck.tsx [\#725](https://github.com/nymtech/nym/pull/725) ([jessgess](https://github.com/jessgess)) -- Rust nymd/cosmwasm client [\#724](https://github.com/nymtech/nym/pull/724) ([jstuczyn](https://github.com/jstuczyn)) +- Rust nyxd/cosmwasm client [\#724](https://github.com/nymtech/nym/pull/724) ([jstuczyn](https://github.com/jstuczyn)) - Removed wasm feature bypassing cyclic dependencies [\#723](https://github.com/nymtech/nym/pull/723) ([jstuczyn](https://github.com/jstuczyn)) - Updated used sphinx dependency to the most recent revision [\#722](https://github.com/nymtech/nym/pull/722) ([jstuczyn](https://github.com/jstuczyn)) - update state management and validation [\#721](https://github.com/nymtech/nym/pull/721) ([fmtabbara](https://github.com/fmtabbara)) @@ -598,10 +781,8 @@ The release also include some additional work for distributed key generation in - Bond and delegation alerts [\#698](https://github.com/nymtech/nym/pull/698) ([fmtabbara](https://github.com/fmtabbara)) - Bugfix/network monitor version check [\#697](https://github.com/nymtech/nym/pull/697) ([jstuczyn](https://github.com/jstuczyn)) - Feature/other containers [\#692](https://github.com/nymtech/nym/pull/692) ([neacsu](https://github.com/neacsu)) -- Using validator API instead of nymd [\#690](https://github.com/nymtech/nym/pull/690) ([futurechimp](https://github.com/futurechimp)) +- Using validator API instead of nyxd [\#690](https://github.com/nymtech/nym/pull/690) ([futurechimp](https://github.com/futurechimp)) - Hang coconut issuance off the validator-api [\#679](https://github.com/nymtech/nym/pull/679) ([durch](https://github.com/durch)) - Update hmac and blake3 [\#673](https://github.com/nymtech/nym/pull/673) ([durch](https://github.com/durch)) - - -\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* +\* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_ diff --git a/Cargo.lock b/Cargo.lock index 822fa26726..8db896935f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,11 +16,12 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" dependencies = [ - "generic-array 0.14.5", + "crypto-common", + "generic-array 0.14.6", ] [[package]] @@ -29,7 +30,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cipher 0.3.0", "cpufeatures", "ctr 0.8.0", @@ -38,25 +39,25 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cipher 0.4.3", "cpufeatures", ] [[package]] name = "aes-gcm" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" dependencies = [ "aead", - "aes 0.7.5", - "cipher 0.3.0", - "ctr 0.8.0", + "aes 0.8.2", + "cipher 0.4.3", + "ctr 0.9.2", "ghash", "subtle 2.4.1", ] @@ -67,34 +68,40 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.8", "once_cell", "version_check", ] [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] -name = "ansi_term" -version = "0.12.1" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "winapi", + "libc", ] [[package]] -name = "anyhow" -version = "1.0.56" +name = "anes" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "arrayref" @@ -108,6 +115,17 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + [[package]] name = "async-stream" version = "0.3.3" @@ -131,9 +149,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", @@ -173,7 +191,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -194,11 +212,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "bandwidth-claim-contract" -version = "0.1.0" +name = "axum" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc" dependencies = [ - "schemars", + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", "serde", + "sync_wrapper", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", ] [[package]] @@ -209,15 +265,27 @@ checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "base64ct" -version = "1.0.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" [[package]] name = "binascii" @@ -225,15 +293,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" -[[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.3.0" @@ -245,7 +304,7 @@ dependencies = [ "k256", "once_cell", "pbkdf2", - "rand_core 0.6.3", + "rand_core 0.6.4", "ripemd160", "sha2 0.9.9", "subtle 2.4.1", @@ -279,9 +338,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", @@ -303,28 +362,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" dependencies = [ "arrayref", "arrayvec", "cc", - "cfg-if 1.0.0", + "cfg-if", "constant_time_eq", - "digest 0.10.3", -] - -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding 0.1.5", - "byte-tools", - "byteorder", - "generic-array 0.12.4", + "digest 0.10.6", ] [[package]] @@ -333,26 +380,17 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding 0.2.1", - "generic-array 0.14.5", + "block-padding", + "generic-array 0.14.6", ] [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", + "generic-array 0.14.6", ] [[package]] @@ -371,7 +409,7 @@ dependencies = [ "ff 0.10.1", "group 0.10.0", "pairing 0.20.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.1", ] @@ -381,10 +419,10 @@ version = "0.6.0" source = "git+https://github.com/jstuczyn/bls12_381?branch=gt-serialisation#10fb6f700bfda17c8475af3bfd31e3fec15f2278" dependencies = [ "digest 0.9.0", - "ff 0.11.0", + "ff 0.11.1", "group 0.11.0", "pairing 0.21.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.1", "zeroize", ] @@ -398,23 +436,11 @@ dependencies = [ "sha2 0.9.9", ] -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byte-tools" @@ -430,34 +456,31 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cast" -version = "0.2.7" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" -dependencies = [ - "rustc_version 0.4.0", -] +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -475,26 +498,79 @@ dependencies = [ ] [[package]] -name = "chrono" -version = "0.4.19" +name = "chacha20" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08" dependencies = [ - "libc", + "cfg-if", + "cipher 0.4.3", + "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.3", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "serde", - "time 0.1.44", + "time 0.1.45", + "wasm-bindgen", "winapi", ] +[[package]] +name = "ciborium" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" + +[[package]] +name = "ciborium-ll" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -505,62 +581,62 @@ checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] name = "clap" -version = "2.34.0" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "bitflags", - "textwrap 0.11.0", - "unicode-width", + "clap_lex 0.2.4", + "indexmap", + "textwrap", ] [[package]] name = "clap" -version = "3.2.8" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" +checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" dependencies = [ - "atty", "bitflags", "clap_derive", - "clap_lex", - "indexmap", + "clap_lex 0.3.1", + "is-terminal", "once_cell", "strsim", "termcolor", - "textwrap 0.15.0", ] [[package]] name = "clap_complete" -version = "3.2.4" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4179da71abd56c26b54dd0c248cc081c1f43b0a1a7e8448e28e57a29baa993d" +checksum = "3d6540eedc41f8a5a76cf3d8d458057dcdf817be4158a55b5f861f7a5483de75" dependencies = [ - "clap 3.2.8", + "clap 4.1.4", ] [[package]] name = "clap_complete_fig" -version = "3.2.4" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed37b4c0c1214673eba6ad8ea31666626bf72be98ffb323067d973c48b4964b9" +checksum = "cf0c76d8fcf782a1102ccfcd10ca8246e7fdd609c1cd6deddbb96cb638e9bb5c" dependencies = [ - "clap 3.2.8", + "clap 4.1.4", "clap_complete", ] [[package]] name = "clap_derive" -version = "3.2.7" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", @@ -577,21 +653,19 @@ dependencies = [ ] [[package]] -name = "client-connections" -version = "0.1.0" +name = "clap_lex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" dependencies = [ - "futures", - "log", + "os_str_bytes", ] [[package]] name = "client-core" -version = "1.1.2" +version = "1.1.13" dependencies = [ "async-trait", - "client-connections", - "config", - "crypto", "dashmap 5.4.0", "dirs", "futures", @@ -600,21 +674,25 @@ dependencies = [ "gloo-timers", "humantime-serde", "log", - "nonexhaustive-delayqueue", - "nymsphinx", - "pemstore", + "nym-config", + "nym-crypto", + "nym-nonexhaustive-delayqueue", + "nym-pemstore", + "nym-sphinx", + "nym-task", + "nym-topology", "rand 0.7.3", "serde", "serde_json", "sqlx 0.6.2", "tap", - "task", "tempfile", "thiserror", "time 0.3.17", "tokio", "tokio-stream", - "topology", + "tokio-tungstenite 0.14.0", + "tungstenite 0.13.0", "url", "validator-client", "wasm-bindgen", @@ -633,36 +711,13 @@ dependencies = [ ] [[package]] -name = "coconut-bandwidth-contract-common" -version = "0.1.0" +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ - "cosmwasm-std", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-dkg-common" -version = "0.1.0" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "cw-utils", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-interface" -version = "0.1.0" -dependencies = [ - "bs58", - "getset", - "nymcoconut", - "serde", - "thiserror", + "termcolor", + "unicode-width", ] [[package]] @@ -678,9 +733,9 @@ dependencies = [ [[package]] name = "comfy-table" -version = "6.0.0" +version = "6.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121d8a5b0346092c18a4b2fd6f620d7a06f0eb7ac0a45860939a0884bc579c56" +checksum = "6e7b787b0dc42e8111badfdbe4c3059158ccb2db8780352fa1b01e8ccf45cc4d" dependencies = [ "crossterm", "strum 0.24.1", @@ -689,45 +744,31 @@ dependencies = [ ] [[package]] -name = "completions" -version = "0.1.0" +name = "concurrent-queue" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" dependencies = [ - "clap 3.2.8", - "clap_complete", - "clap_complete_fig", -] - -[[package]] -name = "config" -version = "0.1.0" -dependencies = [ - "cfg-if 1.0.0", - "handlebars", - "log", - "network-defaults", - "serde", - "toml", - "url", + "crossbeam-utils", ] [[package]] name = "console-api" -version = "0.1.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc347c19eb5b940f396ac155822caee6662f850d97306890ac3773ed76c90c5a" +checksum = "e57ff02e8ad8e06ab9731d5dc72dc23bef9200778eae1a89d555d8c42e5d4a86" dependencies = [ - "prost 0.9.0", - "prost-types 0.9.0", + "prost 0.11.6", + "prost-types 0.11.6", "tonic", - "tonic-build", "tracing-core", ] [[package]] name = "console-subscriber" -version = "0.1.3" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "565a7dfea2d10dd0e5c57cc394d5d441b1910960d8c9211ed14135e0e6ec3a20" +checksum = "22a3a81dfaf6b66bce5d159eddae701e3a002f194d378cbf7be5f053c281d9be" dependencies = [ "console-api", "crossbeam-channel", @@ -735,7 +776,7 @@ dependencies = [ "futures", "hdrhistogram", "humantime 2.1.0", - "prost-types 0.9.0", + "prost-types 0.11.6", "serde", "serde_json", "thread_local", @@ -755,36 +796,23 @@ checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "constant_time_eq" -version = "0.1.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "contracts-common" -version = "0.1.0" -dependencies = [ - "bs58", - "cosmwasm-std", - "dkg", - "schemars", - "serde", - "serde_json", - "thiserror", -] +checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" [[package]] name = "cookie" -version = "0.16.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "aes-gcm", - "base64", + "base64 0.20.0", "hkdf 0.12.3", "hmac 0.12.1", "percent-encoding", "rand 0.8.5", - "sha2 0.10.2", + "sha2 0.10.6", "subtle 2.4.1", "time 0.3.17", "version_check", @@ -811,7 +839,7 @@ name = "cosmos-sdk-proto" version = "0.12.3" source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d" dependencies = [ - "prost 0.10.3", + "prost 0.10.4", "prost-types 0.10.1", "tendermint-proto", ] @@ -825,11 +853,11 @@ dependencies = [ "cosmos-sdk-proto", "ecdsa", "eyre", - "getrandom 0.2.6", + "getrandom 0.2.8", "k256", - "prost 0.10.3", + "prost 0.10.4", "prost-types 0.10.1", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", "serde_json", "subtle-encoding", @@ -847,7 +875,7 @@ dependencies = [ "digest 0.9.0", "ed25519-zebra", "k256", - "rand_core 0.6.3", + "rand_core 0.6.4", "thiserror", ] @@ -866,7 +894,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" dependencies = [ - "base64", + "base64 0.13.1", "cosmwasm-crypto", "cosmwasm-derive", "forward_ref", @@ -897,11 +925,11 @@ dependencies = [ [[package]] name = "crc" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ - "crc-catalog 2.1.0", + "crc-catalog 2.2.0", ] [[package]] @@ -912,9 +940,9 @@ checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" [[package]] name = "crc-catalog" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" [[package]] name = "crc32fast" @@ -922,7 +950,7 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -930,16 +958,16 @@ name = "credential" version = "0.1.0" dependencies = [ "bip39", - "cfg-if 0.1.10", - "clap 3.2.8", - "coconut-interface", - "completions", - "config", - "credential-storage", - "credentials", - "crypto", - "network-defaults", - "pemstore", + "clap 4.1.4", + "log", + "nym-bin-common", + "nym-coconut-interface", + "nym-config", + "nym-credential-storage", + "nym-credentials", + "nym-crypto", + "nym-network-defaults", + "nym-pemstore", "rand 0.7.3", "serde", "thiserror", @@ -948,42 +976,18 @@ dependencies = [ "validator-client", ] -[[package]] -name = "credential-storage" -version = "0.1.0" -dependencies = [ - "async-trait", - "log", - "sqlx 0.5.11", - "thiserror", - "tokio", -] - -[[package]] -name = "credentials" -version = "0.1.0" -dependencies = [ - "bls12_381 0.5.0", - "coconut-interface", - "cosmrs", - "crypto", - "rand 0.7.3", - "thiserror", - "validator-api-requests", - "validator-client", -] - [[package]] name = "criterion" -version = "0.3.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" dependencies = [ + "anes", "atty", "cast", - "clap 2.34.0", + "ciborium", + "clap 3.2.23", "criterion-plot", - "csv", "itertools", "lazy_static", "num-traits", @@ -992,7 +996,6 @@ dependencies = [ "rayon", "regex", "serde", - "serde_cbor", "serde_derive", "serde_json", "tinytemplate", @@ -1001,9 +1004,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", @@ -1011,70 +1014,68 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ "autocfg 1.1.0", - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", - "lazy_static", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ - "cfg-if 1.0.0", - "lazy_static", + "cfg-if", ] [[package]] name = "crossterm" -version = "0.23.2" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" dependencies = [ "bitflags", "crossterm_winapi", "libc", "mio", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "signal-hook", "signal-hook-mio", "winapi", @@ -1095,49 +1096,26 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto" -version = "0.1.0" -dependencies = [ - "aes 0.8.1", - "blake3", - "bs58", - "cipher 0.4.3", - "ctr 0.9.1", - "digest 0.10.3", - "ed25519-dalek", - "generic-array 0.14.5", - "hkdf 0.12.3", - "hmac 0.12.1", - "nymsphinx-types", - "pemstore", - "rand 0.7.3", - "rand_chacha 0.2.2", - "serde", - "serde_bytes", - "subtle-encoding", - "x25519-dalek", -] - [[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.5", - "rand_core 0.6.3", + "generic-array 0.14.6", + "rand_core 0.6.4", "subtle 2.4.1", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", + "rand_core 0.6.4", "typenum", ] @@ -1157,32 +1135,10 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "subtle 2.4.1", ] -[[package]] -name = "csv" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -dependencies = [ - "bstr", - "csv-core", - "itoa 0.4.8", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - [[package]] name = "ct-logs" version = "0.8.0" @@ -1203,9 +1159,9 @@ dependencies = [ [[package]] name = "ctr" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d14f329cfbaf5d0e06b5e87fff7e265d2673c5ea7d2c27691a2c107db1442a0" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ "cipher 0.4.3", ] @@ -1220,6 +1176,36 @@ dependencies = [ "rustc_version 0.2.3", ] +[[package]] +name = "curl" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.61+curl-8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d05c10f541ae6f3bc5b3d923c20001f47db7d5f0b2bc6ad16490133842db79" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1309,6 +1295,50 @@ dependencies = [ "serde", ] +[[package]] +name = "cxx" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc831ee6a32dd495436e317595e639a587aa9907bef96fe6e6abc290ab6204e9" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94331d54f1b1a8895cd81049f7eaaaef9d05a7dcb4d1fd08bf3ff0806246789d" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dcd35ba14ca9b40d6e4b4b39961f23d835dbb8eed74565ded361d93e1feb8a" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bbeb29798b407ccd82a3324ade1a7286e0d29851475990b612670f6f5124d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.13.4" @@ -1350,7 +1380,7 @@ version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "num_cpus", ] @@ -1360,11 +1390,11 @@ version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "hashbrown 0.12.3", "lock_api", "once_cell", - "parking_lot_core 0.9.4", + "parking_lot_core 0.9.7", ] [[package]] @@ -1424,16 +1454,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] name = "digest" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", "subtle 2.4.1", ] @@ -1458,28 +1488,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dkg" -version = "0.1.0" -dependencies = [ - "bitvec", - "bls12_381 0.6.0", - "bs58", - "criterion", - "ff 0.11.0", - "group 0.11.0", - "lazy_static", - "pemstore", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.3", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror", - "zeroize", -] - [[package]] name = "doc-comment" version = "0.3.3" @@ -1494,18 +1502,15 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "dotenvy" -version = "0.15.5" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9155c8f4dc55c7470ae9da3f63c6785245093b3f6aeb0f5bf2e968efbba314" -dependencies = [ - "dirs", -] +checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" [[package]] name = "dyn-clone" -version = "1.0.5" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" +checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" [[package]] name = "ecdsa" @@ -1521,9 +1526,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ "serde", "signature", @@ -1546,24 +1551,24 @@ dependencies = [ [[package]] name = "ed25519-zebra" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ "curve25519-dalek", + "hashbrown 0.12.3", "hex", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", "sha2 0.9.9", - "thiserror", "zeroize", ] [[package]] name = "either" -version = "1.6.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" @@ -1574,10 +1579,10 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "ff 0.11.0", - "generic-array 0.14.5", + "ff 0.11.1", + "generic-array 0.14.6", "group 0.11.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "sec1", "subtle 2.4.1", "zeroize", @@ -1585,27 +1590,27 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] name = "enum-iterator" -version = "0.7.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401" dependencies = [ "enum-iterator-derive", ] [[package]] name = "enum-iterator-derive" -version = "0.7.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce" dependencies = [ "proc-macro2", "quote", @@ -1625,6 +1630,27 @@ dependencies = [ "termcolor", ] +[[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-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" @@ -1640,32 +1666,28 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" -[[package]] -name = "execute" -version = "0.1.0" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "explorer-api" -version = "1.1.1" +version = "1.1.13" dependencies = [ "chrono", - "clap 3.2.8", - "contracts-common", - "dotenv", + "clap 4.1.4", + "dotenvy", "humantime-serde", "isocountry", "itertools", "log", - "logging", "maxminddb", - "mixnet-contract-common", - "network-defaults", + "nym-bin-common", + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-network-defaults", + "nym-task", "okapi", "pretty_env_logger", + "rand 0.8.5", + "rand_pcg 0.3.1", + "rand_seeder", "reqwest", "rocket", "rocket_cors", @@ -1673,7 +1695,6 @@ dependencies = [ "schemars", "serde", "serde_json", - "task", "thiserror", "tokio", "validator-client", @@ -1689,17 +1710,11 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -1710,25 +1725,25 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.1", ] [[package]] name = "ff" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2958d04124b9f27f175eaeb9a9f383d026098aa837eadd8ba22c11f13a05b9e" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.1", ] [[package]] name = "figment" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" +checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" dependencies = [ "atomic", "pear", @@ -1738,21 +1753,13 @@ dependencies = [ "version_check", ] -[[package]] -name = "fixedbitset" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" - [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ - "cfg-if 1.0.0", "crc32fast", - "libc", "miniz_oxide", ] @@ -1768,14 +1775,14 @@ dependencies = [ [[package]] name = "flume" -version = "0.10.12" +version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843c03199d0c0ca54bc1ea90ac0d507274c28abcc4f691ae8b4eaa375087c76a" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" dependencies = [ "futures-core", "futures-sink", "pin-project", - "spin 0.9.2", + "spin 0.9.5", ] [[package]] @@ -1801,11 +1808,10 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] @@ -1829,9 +1835,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -1844,9 +1850,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -1854,15 +1860,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -1871,9 +1877,9 @@ dependencies = [ [[package]] name = "futures-intrusive" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" dependencies = [ "futures-core", "lock_api", @@ -1882,15 +1888,30 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -1899,21 +1920,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -1932,19 +1953,21 @@ name = "gateway-client" version = "0.1.0" dependencies = [ "async-trait", - "coconut-interface", - "credential-storage", - "credentials", - "crypto", "futures", "gateway-requests", - "getrandom 0.2.6", + "getrandom 0.2.8", "log", - "network-defaults", - "nymsphinx", - "pemstore", + "mobile-storage", + "nym-coconut-interface", + "nym-credential-storage", + "nym-credentials", + "nym-crypto", + "nym-network-defaults", + "nym-pemstore", + "nym-sphinx", + "nym-task", "rand 0.7.3", - "task", + "serde", "thiserror", "tokio", "tokio-stream", @@ -1963,13 +1986,13 @@ name = "gateway-requests" version = "0.1.0" dependencies = [ "bs58", - "coconut-interface", - "credentials", - "crypto", "futures", "log", - "nymsphinx", - "pemstore", + "nym-coconut-interface", + "nym-credentials", + "nym-crypto", + "nym-pemstore", + "nym-sphinx", "rand 0.7.3", "serde", "serde_json", @@ -1985,15 +2008,15 @@ checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" [[package]] name = "generator" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" +checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e" dependencies = [ "cc", "libc", "log", "rustversion", - "winapi", + "windows", ] [[package]] @@ -2007,9 +2030,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -2021,7 +2044,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", @@ -2030,14 +2053,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -2055,9 +2078,9 @@ dependencies = [ [[package]] name = "ghash" -version = "0.4.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ "opaque-debug 0.3.0", "polyval", @@ -2065,9 +2088,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c" +checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" dependencies = [ "bitflags", "libc", @@ -2078,15 +2101,15 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "gloo-timers" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" dependencies = [ "futures-channel", "futures-core", @@ -2102,7 +2125,7 @@ checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" dependencies = [ "byteorder", "ff 0.10.1", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.1", ] @@ -2113,16 +2136,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", - "ff 0.11.0", - "rand_core 0.6.3", + "ff 0.11.1", + "rand_core 0.6.4", "subtle 2.4.1", ] [[package]] name = "h2" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -2133,7 +2156,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util", "tracing", ] @@ -2195,11 +2218,11 @@ dependencies = [ [[package]] name = "hdrhistogram" -version = "7.5.0" +version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" +checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "flate2", "nom", @@ -2208,18 +2231,18 @@ dependencies = [ [[package]] name = "headers" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64", + "base64 0.13.1", "bitflags", "bytes", "headers-core", "http", "httpdate", "mime", - "sha-1 0.10.0", + "sha1", ] [[package]] @@ -2242,9 +2265,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" dependencies = [ "unicode-segmentation", ] @@ -2258,6 +2281,21 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -2317,25 +2355,25 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest 0.10.6", ] [[package]] name = "http" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.1", + "itoa", ] [[package]] name = "http-body" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", @@ -2343,10 +2381,16 @@ dependencies = [ ] [[package]] -name = "httparse" -version = "1.6.0" +name = "http-range-header" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -2381,9 +2425,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.18" +version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ "bytes", "futures-channel", @@ -2394,7 +2438,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.1", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -2465,6 +2509,30 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2482,15 +2550,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "inclusion-probability" -version = "0.1.0" -dependencies = [ - "log", - "rand 0.8.5", - "thiserror", -] - [[package]] name = "indenter" version = "0.3.3" @@ -2499,12 +2558,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg 1.1.0", - "hashbrown 0.11.2", + "hashbrown 0.12.3", "serde", ] @@ -2520,7 +2579,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -2538,17 +2597,33 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] -name = "ipnet" -version = "2.4.0" +name = "integer-encoding" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" + +[[package]] +name = "io-lifetimes" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" +dependencies = [ + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "ipnet" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "ipnetwork" @@ -2568,6 +2643,43 @@ dependencies = [ "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "isahc" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "event-listener", + "futures-lite", + "http", + "log", + "once_cell", + "polling", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + [[package]] name = "isocountry" version = "0.3.2" @@ -2580,30 +2692,24 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" -version = "0.4.8" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] @@ -2623,7 +2729,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "ecdsa", "elliptic-curve", "sec1", @@ -2633,9 +2739,12 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] [[package]] name = "keystream" @@ -2688,7 +2797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45ba81a1f5f24396b37211478aff7fbcd605dd4544df8dbed07b9da3c2057aee" dependencies = [ "byteorder", - "cfg-if 1.0.0", + "cfg-if", "hex", "hidapi", "ledger-transport", @@ -2699,15 +2808,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.122" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libgit2-sys" -version = "0.13.2+1.4.2" +version = "0.13.5+1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" +checksum = "51e5ea06c26926f1002dd553fded6cfcdc9784c1f60feeb58368b4d9b07b6dba" dependencies = [ "cc", "libc", @@ -2717,9 +2826,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.2" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "libsqlite3-sys" @@ -2734,9 +2843,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f35facd4a5673cb5a48822be2be1d4236c1c99cb4113cab7061ac720d5bf859" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" dependencies = [ "cc", "libc", @@ -2744,6 +2853,21 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lioness" version = "0.1.2" @@ -2772,24 +2896,16 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "logging" -version = "0.1.0" -dependencies = [ - "log", - "pretty_env_logger", + "cfg-if", ] [[package]] name = "loom" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "generator", "scoped-tls", "serde", @@ -2798,12 +2914,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "matchers" version = "0.1.0" @@ -2815,9 +2925,15 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" [[package]] name = "maxminddb" @@ -2839,15 +2955,15 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg 1.1.0", ] @@ -2866,24 +2982,23 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", - "autocfg 1.1.0", ] [[package]] name = "mio" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.36.1", + "windows-sys 0.42.0", ] [[package]] @@ -2892,28 +3007,11 @@ version = "0.1.0" dependencies = [ "futures", "log", - "nymsphinx", + "nym-sphinx", + "nym-task", "tokio", - "tokio-util 0.7.3", -] - -[[package]] -name = "mixnet-contract-common" -version = "0.1.0" -dependencies = [ - "bs58", - "contracts-common", - "cosmwasm-std", - "humantime-serde", - "log", - "rand_chacha 0.3.1", - "schemars", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "time 0.3.17", - "ts-rs", + "tokio-util", + "tracing", ] [[package]] @@ -2921,31 +3019,42 @@ name = "mixnode-common" version = "0.1.0" dependencies = [ "bytes", - "crypto", "futures", "humantime-serde", "log", - "nymsphinx-acknowledgements", - "nymsphinx-addressing", - "nymsphinx-forwarding", - "nymsphinx-framing", - "nymsphinx-params", - "nymsphinx-types", + "nym-bin-common", + "nym-crypto", + "nym-network-defaults", + "nym-sphinx-acknowledgements", + "nym-sphinx-addressing", + "nym-sphinx-forwarding", + "nym-sphinx-framing", + "nym-sphinx-params", + "nym-sphinx-types", + "nym-task", "rand 0.8.5", "serde", - "task", + "thiserror", "tokio", - "tokio-util 0.7.3", + "tokio-util", + "tracing", "url", "validator-client", - "version-checker", +] + +[[package]] +name = "mobile-storage" +version = "0.1.0" +dependencies = [ + "async-trait", + "thiserror", ] [[package]] name = "multer" -version = "2.0.2" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f8f35e687561d5c1667590911e6698a8cb714a134a7505718a182e7bc9d3836" +checksum = "6ed4198ce7a4cbd2a57af78d28c6fbb57d81ac5f1d6ad79ac6c5587419cbdf22" dependencies = [ "bytes", "encoding_rs", @@ -2955,36 +3064,17 @@ dependencies = [ "log", "memchr", "mime", - "spin 0.9.2", + "spin 0.9.5", "tokio", - "tokio-util 0.6.9", + "tokio-util", "version_check", ] -[[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - -[[package]] -name = "multisig-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "cw3", - "cw3-fixed-multisig", - "cw4", - "schemars", - "serde", -] - [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -2998,19 +3088,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "network-defaults" -version = "0.1.0" -dependencies = [ - "cfg-if 1.0.0", - "dotenv", - "hex-literal", - "once_cell", - "serde", - "thiserror", - "url", -] - [[package]] name = "no-std-compat" version = "0.4.1" @@ -3019,32 +3096,30 @@ checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] -name = "nonexhaustive-delayqueue" -version = "0.1.0" +name = "ntapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" dependencies = [ - "futures-core", - "slab", - "tokio", - "tokio-stream", - "tokio-util 0.7.3", - "wasm-timer", + "winapi", ] [[package]] -name = "ntapi" -version = "0.3.7" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ + "overload", "winapi", ] @@ -3061,9 +3136,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg 1.1.0", "num-traits", @@ -3071,9 +3146,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg 1.1.0", "libm", @@ -3081,14 +3156,116 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] +[[package]] +name = "nym-api" +version = "1.1.14" +dependencies = [ + "anyhow", + "async-trait", + "bip39", + "bs58", + "cfg-if", + "clap 4.1.4", + "console-subscriber", + "cosmwasm-std", + "cw-utils", + "cw3", + "cw4", + "dirs", + "futures", + "gateway-client", + "getset", + "humantime-serde", + "lazy_static", + "log", + "nym-api-requests", + "nym-bin-common", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", + "nym-coconut-interface", + "nym-config", + "nym-contracts-common", + "nym-credential-storage", + "nym-credentials", + "nym-crypto", + "nym-dkg", + "nym-inclusion-probability", + "nym-mixnet-contract-common", + "nym-multisig-contract-common", + "nym-pemstore", + "nym-sphinx", + "nym-task", + "nym-topology", + "nym-vesting-contract-common", + "nymcoconut", + "okapi", + "pin-project", + "pretty_env_logger", + "rand 0.7.3", + "rand 0.8.5", + "reqwest", + "rocket", + "rocket_cors", + "rocket_okapi", + "schemars", + "serde", + "serde_json", + "sqlx 0.6.2", + "tap", + "thiserror", + "time 0.3.17", + "tokio", + "tokio-stream", + "ts-rs", + "url", + "validator-client", +] + +[[package]] +name = "nym-api-requests" +version = "0.1.0" +dependencies = [ + "bs58", + "cosmrs", + "cosmwasm-std", + "getset", + "nym-coconut-interface", + "nym-mixnet-contract-common", + "schemars", + "serde", + "ts-rs", +] + +[[package]] +name = "nym-bandwidth-claim-contract" +version = "0.1.0" +dependencies = [ + "schemars", + "serde", +] + +[[package]] +name = "nym-bin-common" +version = "0.3.0" +dependencies = [ + "clap 4.1.4", + "clap_complete", + "clap_complete_fig", + "log", + "pretty_env_logger", + "semver 0.11.0", + "serde", + "vergen", +] + [[package]] name = "nym-bity-integration" version = "0.1.0" @@ -3106,20 +3283,20 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.2" +version = "1.1.13" dependencies = [ "anyhow", - "base64", + "base64 0.13.1", "bip39", "bs58", - "clap 3.2.8", + "clap 4.1.4", "clap_complete", "clap_complete_fig", - "dotenv", + "dotenvy", "log", - "logging", - "network-defaults", + "nym-bin-common", "nym-cli-commands", + "nym-network-defaults", "pretty_env_logger", "serde", "serde_json", @@ -3132,20 +3309,25 @@ dependencies = [ name = "nym-cli-commands" version = "1.0.0" dependencies = [ - "base64", + "base64 0.13.1", "bip39", "bs58", - "cfg-if 1.0.0", - "clap 3.2.8", + "cfg-if", + "clap 4.1.4", "comfy-table", "cosmrs", "cosmwasm-std", + "cw-utils", "handlebars", "humantime-serde", "k256", "log", - "mixnet-contract-common", - "network-defaults", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", + "nym-mixnet-contract-common", + "nym-multisig-contract-common", + "nym-network-defaults", + "nym-vesting-contract-common", "rand 0.6.5", "serde", "serde_json", @@ -3155,238 +3337,730 @@ dependencies = [ "toml", "url", "validator-client", - "vesting-contract-common", ] [[package]] name = "nym-client" -version = "1.1.2" +version = "1.1.13" dependencies = [ - "clap 3.2.8", - "client-connections", + "clap 4.1.4", "client-core", - "coconut-interface", - "completions", - "config", - "credential-storage", - "credentials", - "crypto", "dirs", "futures", "gateway-client", "gateway-requests", + "lazy_static", "log", - "logging", - "network-defaults", - "nymsphinx", - "pemstore", + "nym-bin-common", + "nym-coconut-interface", + "nym-config", + "nym-credential-storage", + "nym-credentials", + "nym-crypto", + "nym-network-defaults", + "nym-pemstore", + "nym-sphinx", + "nym-task", + "nym-topology", "pretty_env_logger", "rand 0.7.3", "serde", "serde_json", "tap", - "task", "thiserror", "tokio", "tokio-tungstenite 0.14.0", - "topology", "url", "validator-client", - "vergen 5.1.17", - "version-checker", "websocket-requests", ] +[[package]] +name = "nym-coconut-bandwidth-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-dkg-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-interface" +version = "0.1.0" +dependencies = [ + "bs58", + "getset", + "nymcoconut", + "serde", + "thiserror", +] + +[[package]] +name = "nym-config" +version = "0.1.0" +dependencies = [ + "cfg-if", + "handlebars", + "log", + "nym-network-defaults", + "serde", + "toml", + "url", +] + +[[package]] +name = "nym-contracts-common" +version = "0.3.0" +dependencies = [ + "bs58", + "cosmwasm-std", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "nym-credential-storage" +version = "0.1.0" +dependencies = [ + "async-trait", + "log", + "sqlx 0.5.13", + "thiserror", + "tokio", +] + +[[package]] +name = "nym-credentials" +version = "0.1.0" +dependencies = [ + "bls12_381 0.5.0", + "cosmrs", + "nym-api-requests", + "nym-coconut-interface", + "nym-crypto", + "rand 0.7.3", + "thiserror", + "validator-client", +] + +[[package]] +name = "nym-crypto" +version = "0.2.0" +dependencies = [ + "aes 0.8.2", + "blake3", + "bs58", + "cipher 0.4.3", + "ctr 0.9.2", + "digest 0.10.6", + "ed25519-dalek", + "generic-array 0.14.6", + "hkdf 0.12.3", + "hmac 0.12.1", + "nym-pemstore", + "nym-sphinx-types", + "rand 0.7.3", + "rand_chacha 0.2.2", + "serde", + "serde_bytes", + "subtle-encoding", + "thiserror", + "x25519-dalek", +] + +[[package]] +name = "nym-dkg" +version = "0.1.0" +dependencies = [ + "bitvec", + "bls12_381 0.6.0", + "bs58", + "criterion", + "ff 0.11.1", + "group 0.11.0", + "lazy_static", + "nym-contracts-common", + "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-execute" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "nym-gateway" -version = "1.1.2" +version = "1.1.13" dependencies = [ "anyhow", "async-trait", + "atty", "bip39", "bs58", - "clap 3.2.8", - "coconut-interface", + "clap 4.1.4", "colored", - "completions", - "config", - "credentials", - "crypto", "dashmap 4.0.2", "dirs", - "dotenv", + "dotenvy", "futures", "gateway-requests", "humantime-serde", + "lazy_static", "log", - "logging", "mixnet-client", "mixnode-common", - "network-defaults", - "nymsphinx", + "nym-api-requests", + "nym-bin-common", + "nym-coconut-interface", + "nym-config", + "nym-credentials", + "nym-crypto", + "nym-network-defaults", + "nym-pemstore", + "nym-sphinx", + "nym-statistics-common", + "nym-task", + "nym-types", "once_cell", - "pemstore", "pretty_env_logger", "rand 0.7.3", "serde", - "sqlx 0.5.11", - "statistics-common", + "serde_json", + "sqlx 0.5.13", "subtle-encoding", "thiserror", "tokio", "tokio-stream", "tokio-tungstenite 0.14.0", - "tokio-util 0.7.3", + "tokio-util", "url", - "validator-api-requests", "validator-client", - "vergen 5.1.17", - "version-checker", +] + +[[package]] +name = "nym-group-contract-common" +version = "0.1.0" +dependencies = [ + "cw4", + "schemars", + "serde", +] + +[[package]] +name = "nym-inclusion-probability" +version = "0.1.0" +dependencies = [ + "log", + "rand 0.8.5", + "thiserror", +] + +[[package]] +name = "nym-mixnet-contract-common" +version = "0.3.0" +dependencies = [ + "bs58", + "cosmwasm-std", + "humantime-serde", + "log", + "nym-contracts-common", + "rand_chacha 0.3.1", + "schemars", + "serde", + "serde_json", + "serde_repr", + "thiserror", + "time 0.3.17", + "ts-rs", ] [[package]] name = "nym-mixnode" -version = "1.1.2" +version = "1.1.14" dependencies = [ "anyhow", + "atty", "bs58", - "clap 3.2.8", + "clap 4.1.4", "colored", - "completions", - "config", - "crypto", "cupid", "dirs", - "dotenv", "futures", "humantime-serde", "lazy_static", "log", - "logging", "mixnet-client", "mixnode-common", - "nonexhaustive-delayqueue", - "nymsphinx", - "nymsphinx-params", - "nymsphinx-types", - "pemstore", + "nym-bin-common", + "nym-config", + "nym-crypto", + "nym-nonexhaustive-delayqueue", + "nym-pemstore", + "nym-sphinx", + "nym-sphinx-params", + "nym-sphinx-types", + "nym-task", + "nym-topology", + "nym-types", + "opentelemetry", + "opentelemetry-jaeger", "pretty_env_logger", "rand 0.7.3", "rocket", "serde", + "serde_json", "sysinfo", - "task", "tokio", - "tokio-util 0.7.3", + "tokio-util", "toml", - "topology", + "tracing", + "tracing-flame", + "tracing-opentelemetry", + "tracing-subscriber", "url", "validator-client", - "vergen 5.1.17", - "version-checker", +] + +[[package]] +name = "nym-multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw3-fixed-multisig", + "cw4", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-network-defaults" +version = "0.1.0" +dependencies = [ + "cfg-if", + "dotenvy", + "hex-literal", + "once_cell", + "serde", + "thiserror", + "url", ] [[package]] name = "nym-network-requester" -version = "1.1.2" +version = "1.1.13" dependencies = [ "async-trait", - "clap 3.2.8", - "client-connections", - "completions", + "clap 4.1.4", + "client-core", "dirs", "futures", "ipnetwork 0.20.0", + "lazy_static", "log", - "logging", - "network-defaults", - "nymsphinx", - "ordered-buffer", + "nym-bin-common", + "nym-config", + "nym-crypto", + "nym-network-defaults", + "nym-ordered-buffer", + "nym-sdk", + "nym-service-providers-common", + "nym-socks5-proxy-helpers", + "nym-socks5-requests", + "nym-sphinx", + "nym-statistics-common", + "nym-task", "pretty_env_logger", - "proxy-helpers", "publicsuffix", "rand 0.7.3", "reqwest", "serde", - "socks5-requests", "sqlx 0.6.2", - "statistics-common", - "task", + "tap", "thiserror", "tokio", "tokio-tungstenite 0.17.2", + "url", "websocket-requests", ] [[package]] name = "nym-network-statistics" -version = "1.1.2" +version = "1.1.13" dependencies = [ "dirs", "log", - "logging", + "nym-bin-common", + "nym-statistics-common", "pretty_env_logger", "rocket", "serde", - "sqlx 0.5.11", - "statistics-common", + "sqlx 0.5.13", + "thiserror", + "tokio", +] + +[[package]] +name = "nym-nonexhaustive-delayqueue" +version = "0.1.0" +dependencies = [ + "futures-core", + "slab", + "tokio", + "tokio-stream", + "tokio-util", + "wasm-timer", +] + +[[package]] +name = "nym-ordered-buffer" +version = "0.1.0" +dependencies = [ + "log", + "thiserror", +] + +[[package]] +name = "nym-outfox" +version = "0.1.0" +dependencies = [ + "blake3", + "chacha20", + "chacha20poly1305", + "criterion", + "curve25519-dalek", + "fastrand", + "getrandom 0.2.8", + "rayon", + "thiserror", + "zeroize", +] + +[[package]] +name = "nym-pemstore" +version = "0.2.0" +dependencies = [ + "pem", +] + +[[package]] +name = "nym-sdk" +version = "0.1.0" +dependencies = [ + "client-core", + "futures", + "gateway-client", + "gateway-requests", + "log", + "nym-bin-common", + "nym-crypto", + "nym-network-defaults", + "nym-sphinx", + "nym-task", + "nym-topology", + "pretty_env_logger", + "rand 0.7.3", + "tap", + "thiserror", + "tokio", + "toml", + "url", + "validator-client", +] + +[[package]] +name = "nym-service-providers-common" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "log", + "nym-bin-common", + "nym-sdk", + "nym-sphinx-anonymous-replies", + "serde", + "serde_json", "thiserror", "tokio", ] [[package]] name = "nym-socks5-client" -version = "1.1.2" +version = "1.1.13" dependencies = [ - "clap 3.2.8", - "client-connections", + "clap 4.1.4", "client-core", - "coconut-interface", - "completions", - "config", - "credential-storage", - "credentials", - "crypto", "dirs", "futures", "gateway-client", "gateway-requests", + "lazy_static", "log", - "logging", - "network-defaults", - "nymsphinx", - "ordered-buffer", - "pemstore", + "mobile-storage", + "nym-bin-common", + "nym-coconut-interface", + "nym-config", + "nym-credential-storage", + "nym-credentials", + "nym-crypto", + "nym-network-defaults", + "nym-ordered-buffer", + "nym-pemstore", + "nym-service-providers-common", + "nym-socks5-proxy-helpers", + "nym-socks5-requests", + "nym-sphinx", + "nym-task", + "nym-topology", "pin-project", "pretty_env_logger", - "proxy-helpers", "rand 0.7.3", "serde", "serde_json", - "socks5-requests", "tap", - "task", "thiserror", "tokio", - "topology", "url", "validator-client", - "vergen 5.1.17", - "version-checker", +] + +[[package]] +name = "nym-socks5-proxy-helpers" +version = "0.1.0" +dependencies = [ + "bytes", + "futures", + "log", + "nym-ordered-buffer", + "nym-socks5-requests", + "nym-task", + "tokio", + "tokio-test", + "tokio-util", +] + +[[package]] +name = "nym-socks5-requests" +version = "0.1.0" +dependencies = [ + "nym-service-providers-common", + "nym-sphinx-addressing", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "nym-sphinx" +version = "0.1.0" +dependencies = [ + "nym-crypto", + "nym-mixnet-contract-common", + "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-types", + "nym-topology", + "rand 0.7.3", + "rand_distr", + "thiserror", + "tokio", +] + +[[package]] +name = "nym-sphinx-acknowledgements" +version = "0.1.0" +dependencies = [ + "nym-crypto", + "nym-pemstore", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-types", + "nym-topology", + "rand 0.7.3", + "thiserror", +] + +[[package]] +name = "nym-sphinx-addressing" +version = "0.1.0" +dependencies = [ + "nym-crypto", + "nym-sphinx-types", + "rand 0.7.3", + "serde", + "thiserror", +] + +[[package]] +name = "nym-sphinx-anonymous-replies" +version = "0.1.0" +dependencies = [ + "bs58", + "nym-crypto", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-types", + "nym-topology", + "rand 0.7.3", + "serde", + "thiserror", + "wasm-bindgen", +] + +[[package]] +name = "nym-sphinx-chunking" +version = "0.1.0" +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" +dependencies = [ + "nym-crypto", + "nym-sphinx-acknowledgements", + "nym-sphinx-addressing", + "nym-sphinx-chunking", + "nym-sphinx-forwarding", + "nym-sphinx-params", + "nym-sphinx-types", + "nym-topology", + "rand 0.7.3", + "thiserror", +] + +[[package]] +name = "nym-sphinx-forwarding" +version = "0.1.0" +dependencies = [ + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-types", +] + +[[package]] +name = "nym-sphinx-framing" +version = "0.1.0" +dependencies = [ + "bytes", + "nym-sphinx-params", + "nym-sphinx-types", + "thiserror", + "tokio-util", +] + +[[package]] +name = "nym-sphinx-params" +version = "0.1.0" +dependencies = [ + "nym-crypto", + "nym-sphinx-types", + "thiserror", +] + +[[package]] +name = "nym-sphinx-types" +version = "0.2.0" +dependencies = [ + "sphinx-packet", +] + +[[package]] +name = "nym-statistics-common" +version = "1.0.1" +dependencies = [ + "async-trait", + "log", + "reqwest", + "serde", + "serde_json", + "sqlx 0.5.13", + "thiserror", + "tokio", +] + +[[package]] +name = "nym-task" +version = "0.1.0" +dependencies = [ + "futures", + "log", + "thiserror", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "nym-topology" +version = "0.1.0" +dependencies = [ + "async-trait", + "bs58", + "log", + "nym-bin-common", + "nym-crypto", + "nym-mixnet-contract-common", + "nym-sphinx-addressing", + "nym-sphinx-types", + "rand 0.7.3", + "thiserror", ] [[package]] name = "nym-types" version = "1.0.0" dependencies = [ - "coconut-interface", - "config", "cosmrs", "cosmwasm-std", "eyre", "itertools", "log", - "mixnet-contract-common", + "nym-coconut-interface", + "nym-config", + "nym-mixnet-contract-common", + "nym-vesting-contract", + "nym-vesting-contract-common", "reqwest", "schemars", "serde", @@ -3397,110 +4071,73 @@ dependencies = [ "ts-rs", "url", "validator-client", - "vesting-contract", - "vesting-contract-common", ] [[package]] -name = "nym-validator-api" -version = "1.1.2" +name = "nym-vesting-contract" +version = "1.2.0" dependencies = [ - "anyhow", - "async-trait", - "bs58", - "cfg-if 1.0.0", - "clap 3.2.8", - "coconut-bandwidth-contract-common", - "coconut-dkg-common", - "coconut-interface", - "config", - "console-subscriber", - "contracts-common", + "cosmwasm-derive", "cosmwasm-std", - "credential-storage", - "credentials", - "crypto", - "cw-utils", - "cw3", - "dirs", - "dkg", - "dotenv", - "futures", - "gateway-client", - "getset", - "humantime-serde", - "inclusion-probability", - "log", - "logging", - "mixnet-contract-common", - "multisig-contract-common", - "nymcoconut", - "nymsphinx", - "okapi", - "pemstore", - "pin-project", - "pretty_env_logger", - "rand 0.7.3", - "rand 0.8.5", - "reqwest", - "rocket", - "rocket_cors", - "rocket_okapi", + "cw-storage-plus", + "cw2", + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-vesting-contract-common", + "schemars", + "semver 1.0.16", + "serde", + "thiserror", + "vergen", +] + +[[package]] +name = "nym-vesting-contract-common" +version = "0.3.0" +dependencies = [ + "cosmwasm-std", + "nym-contracts-common", + "nym-mixnet-contract-common", "schemars", "serde", - "serde_json", - "sqlx 0.6.2", - "tap", - "task", - "thiserror", - "time 0.3.17", - "tokio", - "tokio-stream", - "topology", "ts-rs", - "url", - "validator-api-requests", - "validator-client", - "vergen 7.2.1", - "version-checker", ] [[package]] name = "nym-wallet-types" version = "1.0.0" dependencies = [ - "config", "cosmrs", "cosmwasm-std", "hex-literal", - "mixnet-contract-common", - "network-defaults", + "nym-config", + "nym-mixnet-contract-common", + "nym-network-defaults", "nym-types", + "nym-vesting-contract", + "nym-vesting-contract-common", "serde", "serde_json", "strum 0.23.0", "ts-rs", "validator-client", - "vesting-contract", - "vesting-contract-common", ] [[package]] name = "nymcoconut" version = "0.5.0" dependencies = [ - "bincode", "bls12_381 0.6.0", "bs58", "criterion", "digest 0.9.0", - "dkg", "doc-comment", - "ff 0.11.0", - "getrandom 0.2.6", + "ff 0.11.1", + "getrandom 0.2.8", "group 0.11.0", "itertools", - "pemstore", + "nym-dkg", + "nym-pemstore", "rand 0.8.5", "rand_chacha 0.3.1", "serde", @@ -3509,130 +4146,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "nymsphinx" -version = "0.1.0" -dependencies = [ - "crypto", - "mixnet-contract-common", - "nymsphinx-acknowledgements", - "nymsphinx-addressing", - "nymsphinx-anonymous-replies", - "nymsphinx-chunking", - "nymsphinx-cover", - "nymsphinx-forwarding", - "nymsphinx-framing", - "nymsphinx-params", - "nymsphinx-types", - "rand 0.7.3", - "rand_distr", - "thiserror", - "tokio", - "topology", -] - -[[package]] -name = "nymsphinx-acknowledgements" -version = "0.1.0" -dependencies = [ - "crypto", - "nymsphinx-addressing", - "nymsphinx-params", - "nymsphinx-types", - "pemstore", - "rand 0.7.3", - "topology", -] - -[[package]] -name = "nymsphinx-addressing" -version = "0.1.0" -dependencies = [ - "crypto", - "nymsphinx-types", - "rand 0.7.3", - "serde", - "thiserror", -] - -[[package]] -name = "nymsphinx-anonymous-replies" -version = "0.1.0" -dependencies = [ - "bs58", - "crypto", - "nymsphinx-addressing", - "nymsphinx-params", - "nymsphinx-types", - "rand 0.7.3", - "serde", - "thiserror", - "topology", - "wasm-bindgen", -] - -[[package]] -name = "nymsphinx-chunking" -version = "0.1.0" -dependencies = [ - "log", - "nymsphinx-addressing", - "nymsphinx-params", - "nymsphinx-types", - "rand 0.7.3", - "thiserror", -] - -[[package]] -name = "nymsphinx-cover" -version = "0.1.0" -dependencies = [ - "crypto", - "nymsphinx-acknowledgements", - "nymsphinx-addressing", - "nymsphinx-chunking", - "nymsphinx-forwarding", - "nymsphinx-params", - "nymsphinx-types", - "rand 0.7.3", - "thiserror", - "topology", -] - -[[package]] -name = "nymsphinx-forwarding" -version = "0.1.0" -dependencies = [ - "nymsphinx-addressing", - "nymsphinx-params", - "nymsphinx-types", -] - -[[package]] -name = "nymsphinx-framing" -version = "0.1.0" -dependencies = [ - "bytes", - "nymsphinx-params", - "nymsphinx-types", - "tokio-util 0.7.3", -] - -[[package]] -name = "nymsphinx-params" -version = "0.1.0" -dependencies = [ - "crypto", - "nymsphinx-types", -] - -[[package]] -name = "nymsphinx-types" -version = "0.1.0" -dependencies = [ - "sphinx", -] - [[package]] name = "okapi" version = "0.7.0-rc.1" @@ -3647,9 +4160,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "oorandom" @@ -3671,18 +4184,30 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.38" +version = "0.10.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" dependencies = [ "bitflags", - "cfg-if 1.0.0", + "cfg-if", "foreign-types", "libc", "once_cell", + "openssl-macros", "openssl-sys", ] +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -3691,9 +4216,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.72" +version = "0.9.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" dependencies = [ "autocfg 1.1.0", "cc", @@ -3703,17 +4228,115 @@ dependencies = [ ] [[package]] -name = "ordered-buffer" -version = "0.1.0" +name = "opentelemetry" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d6c3d7288a106c0a363e4b0e8d308058d56902adefb16f4936f417ffef086e" dependencies = [ - "log", + "opentelemetry_api", + "opentelemetry_sdk", +] + +[[package]] +name = "opentelemetry-http" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc79add46364183ece1a4542592ca593e6421c60807232f5b8f7a31703825d" +dependencies = [ + "async-trait", + "bytes", + "http", + "isahc", + "opentelemetry_api", +] + +[[package]] +name = "opentelemetry-jaeger" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e785d273968748578931e4dc3b4f5ec86b26e09d9e0d66b55adda7fce742f7a" +dependencies = [ + "async-trait", + "futures", + "futures-executor", + "http", + "isahc", + "once_cell", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-semantic-conventions", + "thiserror", + "thrift", + "tokio", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b02e0230abb0ab6636d18e2ba8fa02903ea63772281340ccac18e0af3ec9eeb" +dependencies = [ + "opentelemetry", +] + +[[package]] +name = "opentelemetry_api" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c24f96e21e7acc813c7a8394ee94978929db2bcc46cf6b5014fc612bf7760c22" +dependencies = [ + "fnv", + "futures-channel", + "futures-util", + "indexmap", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca41c4933371b61c2a2f214bf16931499af4ec90543604ec828f7a625c09113" +dependencies = [ + "async-trait", + "crossbeam-channel", + "dashmap 5.4.0", + "fnv", + "futures-channel", + "futures-executor", + "futures-util", + "once_cell", + "opentelemetry_api", + "percent-encoding", + "rand 0.8.5", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "ordered-float" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7" +dependencies = [ + "num-traits", ] [[package]] name = "os_str_bytes" -version = "6.0.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pairing" @@ -3733,6 +4356,12 @@ dependencies = [ "group 0.11.0", ] +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.11.2" @@ -3741,51 +4370,51 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.4", + "parking_lot_core 0.9.7", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "instant", "libc", "redox_syscall", - "smallvec 1.8.0", + "smallvec 1.10.0", "winapi", ] [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", - "smallvec 1.8.0", - "windows-sys 0.42.0", + "smallvec 1.10.0", + "windows-sys 0.45.0", ] [[package]] name = "paste" -version = "1.0.7" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "pbkdf2" @@ -3852,38 +4481,32 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" dependencies = [ - "base64", + "base64 0.13.1", "once_cell", "regex", ] -[[package]] -name = "pemstore" -version = "0.1.0" -dependencies = [ - "pem", -] - [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" dependencies = [ + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.1.0" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69" dependencies = [ "pest", "pest_generator", @@ -3891,9 +4514,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202" dependencies = [ "pest", "pest_meta", @@ -3904,39 +4527,29 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616" dependencies = [ - "maplit", + "once_cell", "pest", - "sha-1 0.8.2", -] - -[[package]] -name = "petgraph" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" -dependencies = [ - "fixedbitset", - "indexmap", + "sha2 0.10.6", ] [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -3945,9 +4558,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -3968,15 +4581,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "plotters" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" dependencies = [ "num-traits", "plotters-backend", @@ -3987,26 +4600,53 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" dependencies = [ "plotters-backend", ] [[package]] -name = "polyval" -version = "0.5.3" +name = "polling" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +checksum = "7e1f879b2998099c2d69ab9605d145d5b661195627eccc680002c4918a7fb6fa" dependencies = [ - "cfg-if 1.0.0", + "autocfg 1.1.0", + "bitflags", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.45.0", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +dependencies = [ + "cfg-if", "cpufeatures", "opaque-debug 0.3.0", "universal-hash", @@ -4014,9 +4654,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pretty_env_logger" @@ -4054,9 +4694,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] @@ -4076,55 +4716,22 @@ dependencies = [ [[package]] name = "prost" -version = "0.9.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" -dependencies = [ - "bytes", - "prost-derive 0.9.0", -] - -[[package]] -name = "prost" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc03e116981ff7d8da8e5c220e374587b98d294af7ba7dd7fda761158f00086f" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" dependencies = [ "bytes", "prost-derive 0.10.1", ] [[package]] -name = "prost-build" -version = "0.9.0" +name = "prost" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +checksum = "21dc42e00223fc37204bd4aa177e69420c604ca4a183209a8f9de30c6d934698" dependencies = [ "bytes", - "heck 0.3.3", - "itertools", - "lazy_static", - "log", - "multimap", - "petgraph", - "prost 0.9.0", - "prost-types 0.9.0", - "regex", - "tempfile", - "which", -] - -[[package]] -name = "prost-derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", + "prost-derive 0.11.6", ] [[package]] @@ -4141,13 +4748,16 @@ dependencies = [ ] [[package]] -name = "prost-types" -version = "0.9.0" +name = "prost-derive" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +checksum = "8bda8c0881ea9f722eb9629376db3d0b903b462477c1aafcb0566610ac28ac5d" dependencies = [ - "bytes", - "prost 0.9.0", + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -4157,23 +4767,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" dependencies = [ "bytes", - "prost 0.10.3", + "prost 0.10.4", ] [[package]] -name = "proxy-helpers" -version = "0.1.0" +name = "prost-types" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e0526209433e96d83d750dd81a99118edbc55739e7e61a46764fd2ad537788" dependencies = [ "bytes", - "client-connections", - "futures", - "log", - "ordered-buffer", - "socks5-requests", - "task", - "tokio", - "tokio-test", - "tokio-util 0.7.3", + "prost 0.11.6", ] [[package]] @@ -4204,9 +4808,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -4231,7 +4835,7 @@ dependencies = [ "rand_isaac", "rand_jitter", "rand_os", - "rand_pcg", + "rand_pcg 0.1.2", "rand_xorshift", "winapi", ] @@ -4257,7 +4861,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -4287,7 +4891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -4316,11 +4920,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.8", ] [[package]] @@ -4395,6 +4999,24 @@ dependencies = [ "rand_core 0.4.2", ] +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand_seeder" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2890aaef0aa82719a50e808de264f9484b74b442e1a3a0e5ee38243ac40bdb" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_xorshift" version = "0.1.1" @@ -4406,26 +5028,23 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "autocfg 1.1.0", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] @@ -4440,9 +5059,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -4453,25 +5072,25 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.8", "redox_syscall", "thiserror", ] [[package]] name = "ref-cast" -version = "1.0.6" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +checksum = "8c78fb8c9293bcd48ef6fce7b4ca950ceaf21210de6e105a883ee280c0f7b9ed" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.6" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" dependencies = [ "proc-macro2", "quote", @@ -4480,9 +5099,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.5" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -4500,9 +5119,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -4515,11 +5134,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ - "base64", + "base64 0.21.0", "bytes", "encoding_rs", "futures-core", @@ -4531,10 +5150,10 @@ dependencies = [ "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log", "mime", "native-tls", + "once_cell", "percent-encoding", "pin-project-lite", "serde", @@ -4607,7 +5226,7 @@ dependencies = [ "memchr", "multer", "num_cpus", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project-lite", "rand 0.8.5", "ref-cast", @@ -4620,7 +5239,7 @@ dependencies = [ "time 0.3.17", "tokio", "tokio-stream", - "tokio-util 0.7.3", + "tokio-util", "ubyte", "version_check", "yansi", @@ -4676,7 +5295,7 @@ dependencies = [ "pin-project-lite", "ref-cast", "serde", - "smallvec 1.8.0", + "smallvec 1.10.0", "stable-pattern", "state", "time 0.3.17", @@ -4728,7 +5347,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.7", + "semver 1.0.16", +] + +[[package]] +name = "rustix" +version = "0.36.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", ] [[package]] @@ -4737,7 +5370,7 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64", + "base64 0.13.1", "log", "ring", "sct 0.6.1", @@ -4746,9 +5379,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.6" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -4770,24 +5403,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "same-file" @@ -4800,19 +5433,18 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "schemars" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" +checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307" dependencies = [ "dyn-clone", "indexmap", @@ -4823,9 +5455,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" +checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" dependencies = [ "proc-macro2", "quote", @@ -4835,9 +5467,9 @@ dependencies = [ [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -4845,6 +5477,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" + [[package]] name = "sct" version = "0.6.1" @@ -4872,7 +5510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", - "generic-array 0.14.5", + "generic-array 0.14.6", "pkcs8", "subtle 2.4.1", "zeroize", @@ -4880,9 +5518,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.6.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", @@ -4893,9 +5531,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -4921,9 +5559,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.7" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" [[package]] name = "semver-parser" @@ -4942,9 +5580,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] @@ -4960,28 +5598,18 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.6" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" dependencies = [ "serde", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -5001,20 +5629,20 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ - "itoa 1.0.1", + "itoa", "ryu", "serde", ] [[package]] name = "serde_repr" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" dependencies = [ "proc-macro2", "quote", @@ -5028,23 +5656,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.1", + "itoa", "ryu", "serde", ] -[[package]] -name = "sha-1" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - [[package]] name = "sha-1" version = "0.9.8" @@ -5052,7 +5668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", @@ -5060,13 +5676,24 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.6", +] + +[[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.6", ] [[package]] @@ -5076,7 +5703,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", @@ -5084,13 +5711,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -5146,19 +5773,33 @@ dependencies = [ [[package]] name = "signature" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] [[package]] name = "smallvec" @@ -5171,15 +5812,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "snafu" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ba99b054b22972ee794cf04e5ef572da1229e33b65f3c57abbff0525a454" +checksum = "cb0656e7e3ffb70f6c39b3c2a86332bb74aa3c679da781642590f3c1118c5045" dependencies = [ "doc-comment", "snafu-derive", @@ -5187,11 +5828,11 @@ dependencies = [ [[package]] name = "snafu-derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5e79cdebbabaebb06a9bdbaedc7f159b410461f63611d4d0e3fb0fab8fed850" +checksum = "475b3bbe5245c26f2d8a6f62d67c1f30eb9fffeccee721c45d162c3ebbdf81b2" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro2", "quote", "syn", @@ -5199,26 +5840,19 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", ] [[package]] -name = "socks5-requests" +name = "sphinx-packet" version = "0.1.0" -dependencies = [ - "nymsphinx-addressing", - "thiserror", -] - -[[package]] -name = "sphinx" -version = "0.1.0" -source = "git+https://github.com/nymtech/sphinx?rev=e05a1992522ed0afd3c6fcac160313ffc9bb306a#e05a1992522ed0afd3c6fcac160313ffc9bb306a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" dependencies = [ "aes 0.7.5", "arrayref", @@ -5246,9 +5880,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc" dependencies = [ "lock_api", ] @@ -5276,9 +5910,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87e292b4291f154971a43c3774364e2cbcaec599d3f5bf6fa9d122885dbc38a" +checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" dependencies = [ "itertools", "nom", @@ -5287,12 +5921,12 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc15591eb44ffb5816a4a70a7efd5dd87bfd3aa84c4c200401c4396140525826" +checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b" dependencies = [ "sqlx-core 0.5.13", - "sqlx-macros 0.5.11", + "sqlx-macros 0.5.13", ] [[package]] @@ -5330,7 +5964,7 @@ dependencies = [ "hashlink 0.7.0", "hex", "indexmap", - "itoa 1.0.1", + "itoa", "libc", "libsqlite3-sys", "log", @@ -5339,8 +5973,8 @@ dependencies = [ "paste", "percent-encoding", "rustls 0.19.1", - "sha2 0.10.2", - "smallvec 1.8.0", + "sha2 0.10.6", + "smallvec 1.10.0", "sqlformat 0.1.8", "sqlx-rt 0.5.13", "stringprep", @@ -5363,7 +5997,7 @@ dependencies = [ "byteorder", "bytes", "chrono", - "crc 3.0.0", + "crc 3.0.1", "crossbeam-queue", "dotenvy", "either", @@ -5377,7 +6011,7 @@ dependencies = [ "hashlink 0.8.1", "hex", "indexmap", - "itoa 1.0.1", + "itoa", "libc", "libsqlite3-sys", "log", @@ -5385,32 +6019,32 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rustls 0.20.6", + "rustls 0.20.8", "rustls-pemfile", - "sha2 0.10.2", - "smallvec 1.8.0", - "sqlformat 0.2.0", + "sha2 0.10.6", + "smallvec 1.10.0", + "sqlformat 0.2.1", "sqlx-rt 0.6.2", "stringprep", "thiserror", "tokio-stream", "url", - "webpki-roots 0.22.4", + "webpki-roots 0.22.6", ] [[package]] name = "sqlx-macros" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee35713129561f5e55c554bba1c378e2a7e67f81257b7311183de98c50e6f94" +checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1" dependencies = [ "dotenv", "either", - "heck 0.3.3", + "heck 0.4.1", "once_cell", "proc-macro2", "quote", - "sha2 0.9.9", + "sha2 0.10.6", "sqlx-core 0.5.13", "sqlx-rt 0.5.13", "syn", @@ -5425,11 +6059,11 @@ checksum = "b850fa514dc11f2ee85be9d055c512aa866746adfacd1cb42d867d68e6a5b0d9" dependencies = [ "dotenvy", "either", - "heck 0.4.0", + "heck 0.4.1", "once_cell", "proc-macro2", "quote", - "sha2 0.10.2", + "sha2 0.10.6", "sqlx-core 0.6.2", "sqlx-rt 0.6.2", "syn", @@ -5482,20 +6116,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "statistics-common" -version = "1.0.1" -dependencies = [ - "async-trait", - "log", - "reqwest", - "serde", - "serde_json", - "sqlx 0.5.11", - "thiserror", - "tokio", -] - [[package]] name = "stringprep" version = "0.1.2" @@ -5546,7 +6166,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", @@ -5576,15 +6196,21 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.104" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", "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" @@ -5599,11 +6225,11 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.24.1" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a8e71535da31837213ac114531d31def75d7aebd133264e420a3451fa7f703" +checksum = "975fe381e0ecba475d4acff52466906d95b153a40324956552e027b2a9eaa89e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "core-foundation-sys", "libc", "ntapi", @@ -5618,25 +6244,13 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "task" -version = "0.1.0" -dependencies = [ - "futures", - "log", - "thiserror", - "tokio", - "wasm-bindgen", - "wasm-bindgen-futures", -] - [[package]] name = "tempfile" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fastrand", "libc", "redox_syscall", @@ -5659,7 +6273,7 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost 0.10.3", + "prost 0.10.4", "prost-types 0.10.1", "ripemd160", "serde", @@ -5699,7 +6313,7 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.10.3", + "prost 0.10.4", "prost-types 0.10.1", "serde", "serde_bytes", @@ -5717,7 +6331,7 @@ dependencies = [ "bytes", "flex-error", "futures", - "getrandom 0.2.6", + "getrandom 0.2.8", "http", "hyper", "hyper-proxy", @@ -5742,42 +6356,33 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" -version = "0.11.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -5794,10 +6399,32 @@ dependencies = [ ] [[package]] -name = "time" -version = "0.1.44" +name = "threadpool" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "thrift" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09678c4cdbb4eed72e18b7c2af1329c69825ed16fcbac62d083fc3e2b0590ff0" +dependencies = [ + "byteorder", + "integer-encoding", + "log", + "ordered-float", + "threadpool", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", @@ -5810,7 +6437,7 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa 1.0.1", + "itoa", "js-sys", "serde", "time-core", @@ -5844,9 +6471,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.22.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg 1.1.0", "bytes", @@ -5854,13 +6481,13 @@ dependencies = [ "memchr", "mio", "num_cpus", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "tracing", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -5875,9 +6502,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -5886,9 +6513,9 @@ dependencies = [ [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -5911,21 +6538,21 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.6", + "rustls 0.20.8", "tokio", "webpki 0.22.0", ] [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util 0.7.3", + "tokio-util", ] [[package]] @@ -5968,23 +6595,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", @@ -5997,22 +6610,23 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] name = "tonic" -version = "0.6.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" dependencies = [ "async-stream", "async-trait", - "base64", + "axum", + "base64 0.13.1", "bytes", "futures-core", "futures-util", @@ -6023,11 +6637,11 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.9.0", - "prost-derive 0.9.0", + "prost 0.11.6", + "prost-derive 0.11.6", "tokio", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -6035,38 +6649,11 @@ dependencies = [ "tracing-futures", ] -[[package]] -name = "tonic-build" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" -dependencies = [ - "proc-macro2", - "prost-build", - "quote", - "syn", -] - -[[package]] -name = "topology" -version = "0.1.0" -dependencies = [ - "bs58", - "crypto", - "log", - "mixnet-contract-common", - "nymsphinx-addressing", - "nymsphinx-types", - "rand 0.7.3", - "thiserror", - "version-checker", -] - [[package]] name = "tower" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", @@ -6076,31 +6663,50 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util", "tower-layer", "tower-service", "tracing", ] [[package]] -name = "tower-layer" -version = "0.3.1" +name = "tower-http" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[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.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.33" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80b9fa4360528139bc96100c160b7ae879f5567f49f1782b0b02035b0358ebf3" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -6109,9 +6715,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.20" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -6120,12 +6726,23 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.24" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90442985ee2f57c9e1b548ee72ae842f4a9a20e3f417cc38dbc5dc684d9bb4ee" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-flame" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bae117ee14789185e129aaee5d93750abe67fdc5a9a62650452bfe4e122a3a9" dependencies = [ "lazy_static", - "valuable", + "tracing", + "tracing-subscriber", ] [[package]] @@ -6140,9 +6757,9 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", "log", @@ -6150,17 +6767,31 @@ dependencies = [ ] [[package]] -name = "tracing-subscriber" -version = "0.3.11" +name = "tracing-opentelemetry" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" +checksum = "21ebb87a95ea13271332df069020513ab70bdb5637ca42d6e492dc3bbbad48de" +dependencies = [ + "once_cell", + "opentelemetry", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ - "ansi_term", - "lazy_static", "matchers", + "nu-ansi-term", + "once_cell", "regex", "sharded-slab", - "smallvec 1.8.0", + "smallvec 1.10.0", "thread_local", "tracing", "tracing-core", @@ -6169,15 +6800,15 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ts-rs" -version = "6.1.2" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d26cba30c9b3a2f537f765cf754126f11c983b7426d280ae0b4cef2374cab98" +checksum = "4added4070a4fdf9df03457206cd2e4b12417c8560a2954d91ffcbe60177a56a" dependencies = [ "thiserror", "ts-rs-macros", @@ -6188,21 +6819,21 @@ name = "ts-rs-cli" version = "0.1.0" dependencies = [ "anyhow", - "mixnet-contract-common", + "nym-api-requests", + "nym-mixnet-contract-common", "nym-types", + "nym-vesting-contract-common", "nym-wallet-types", "ts-rs", - "validator-api-requests", "validator-client", - "vesting-contract-common", "walkdir", ] [[package]] name = "ts-rs-macros" -version = "6.1.2" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd8e302fbcf5b60dfa1df443535967511442c5ce4eac8b4c9fa811f2274280a4" +checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff" dependencies = [ "Inflector", "proc-macro2", @@ -6217,7 +6848,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "bytes", "http", @@ -6237,14 +6868,14 @@ version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "bytes", "http", "httparse", "log", "rand 0.8.5", - "sha-1 0.10.0", + "sha-1 0.10.1", "thiserror", "url", "utf-8", @@ -6252,30 +6883,30 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ubyte" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe" +checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" dependencies = [ "serde", ] [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy", @@ -6285,9 +6916,9 @@ dependencies = [ [[package]] name = "uncased" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" dependencies = [ "serde", "version_check", @@ -6314,15 +6945,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -6335,21 +6966,21 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "unicode_categories" @@ -6359,11 +6990,11 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" dependencies = [ - "generic-array 0.14.5", + "crypto-common", "subtle 2.4.1", ] @@ -6375,13 +7006,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", "serde", ] @@ -6398,46 +7028,36 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -[[package]] -name = "validator-api-requests" -version = "0.1.0" -dependencies = [ - "bs58", - "coconut-interface", - "cosmrs", - "cosmwasm-std", - "getset", - "mixnet-contract-common", - "schemars", - "serde", - "ts-rs", -] - [[package]] name = "validator-client" version = "0.1.0" dependencies = [ "async-trait", - "base64", + "base64 0.13.1", "bip39", - "coconut-bandwidth-contract-common", - "coconut-dkg-common", - "coconut-interface", "colored", - "config", - "contracts-common", "cosmrs", "cosmwasm-std", "cw3", - "execute", + "cw4", "flate2", "futures", "itertools", "log", - "mixnet-contract-common", - "multisig-contract-common", - "network-defaults", - "prost 0.10.3", + "nym-api-requests", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", + "nym-coconut-interface", + "nym-config", + "nym-contracts-common", + "nym-execute", + "nym-group-contract-common", + "nym-mixnet-contract-common", + "nym-multisig-contract-common", + "nym-network-defaults", + "nym-vesting-contract", + "nym-vesting-contract-common", + "prost 0.10.4", "reqwest", "serde", "serde_json", @@ -6446,9 +7066,7 @@ dependencies = [ "tokio", "ts-rs", "url", - "validator-api-requests", - "vesting-contract", - "vesting-contract-common", + "zeroize", ] [[package]] @@ -6465,29 +7083,12 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "5.1.17" +version = "7.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e" +checksum = "447f9238a4553957277b3ee09d80babeae0811f1b3baefb093de1c0448437a37" dependencies = [ "anyhow", - "cfg-if 1.0.0", - "chrono", - "enum-iterator", - "getset", - "git2", - "rustc_version 0.4.0", - "rustversion", - "thiserror", -] - -[[package]] -name = "vergen" -version = "7.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f44ef1afcf5979e34748c12595f9589f3dc4e34abf156fb6d95f9b835568dc" -dependencies = [ - "anyhow", - "cfg-if 1.0.0", + "cfg-if", "enum-iterator", "getset", "git2", @@ -6497,13 +7098,6 @@ dependencies = [ "time 0.3.17", ] -[[package]] -name = "version-checker" -version = "0.1.0" -dependencies = [ - "semver 0.11.0", -] - [[package]] name = "version_check" version = "0.9.4" @@ -6511,31 +7105,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "vesting-contract" -version = "1.1.2" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "cw-storage-plus", - "mixnet-contract-common", - "schemars", - "serde", - "thiserror", - "vergen 5.1.17", - "vesting-contract-common", -] - -[[package]] -name = "vesting-contract-common" -version = "0.1.0" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "mixnet-contract-common", - "schemars", - "serde", - "ts-rs", -] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" @@ -6582,7 +7155,7 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] @@ -6603,11 +7176,11 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.28" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", @@ -6670,9 +7243,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -6709,9 +7282,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.4" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki 0.22.0", ] @@ -6720,22 +7293,11 @@ dependencies = [ name = "websocket-requests" version = "0.1.0" dependencies = [ - "nymsphinx", + "nym-sphinx", "serde", "serde_json", ] -[[package]] -name = "which" -version = "4.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" -dependencies = [ - "either", - "lazy_static", - "libc", -] - [[package]] name = "winapi" version = "0.3.9" @@ -6768,16 +7330,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.36.1" +name = "windows" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" 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", + "windows_aarch64_msvc 0.39.0", + "windows_i686_gnu 0.39.0", + "windows_i686_msvc 0.39.0", + "windows_x86_64_gnu 0.39.0", + "windows_x86_64_msvc 0.39.0", ] [[package]] @@ -6787,85 +7349,109 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "winreg" @@ -6878,9 +7464,9 @@ dependencies = [ [[package]] name = "wyz" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] @@ -6905,18 +7491,18 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zeroize" -version = "1.4.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e03bd46aeb..f1e6937055 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,27 +23,28 @@ members = [ "clients/native/websocket-requests", "clients/socks5", "common/bandwidth-claim-contract", + "common/bin-common", "common/client-libs/gateway-client", "common/client-libs/mixnet-client", "common/client-libs/validator-client", - "common/client-connections", "common/coconut-interface", "common/commands", "common/config", "common/cosmwasm-smart-contracts/coconut-bandwidth-contract", "common/cosmwasm-smart-contracts/coconut-dkg", "common/cosmwasm-smart-contracts/contracts-common", + "common/cosmwasm-smart-contracts/group-contract", "common/cosmwasm-smart-contracts/mixnet-contract", "common/cosmwasm-smart-contracts/multisig-contract", "common/cosmwasm-smart-contracts/vesting-contract", + "common/mobile-storage", "common/credential-storage", "common/credentials", "common/crypto", - "common/crypto/dkg", + "common/dkg", "common/execute", "common/inclusion-probability", "common/ledger", - "common/logging", "common/mixnode-common", "common/network-defaults", "common/nonexhaustive-delayqueue", @@ -66,16 +67,18 @@ members = [ "common/topology", "common/types", "common/wasm-utils", - "common/completions", "explorer-api", "gateway", "gateway/gateway-requests", "integrations/bity", "mixnode", + "sdk/rust/nym-sdk", + "service-providers/common", "service-providers/network-requester", "service-providers/network-statistics", - "validator-api", - "validator-api/validator-api-requests", + "nym-api", + "nym-api/nym-api-requests", + "nym-outfox", "tools/nym-cli", "tools/ts-rs-cli" ] @@ -87,8 +90,41 @@ default-members = [ "service-providers/network-requester", "service-providers/network-statistics", "mixnode", - "validator-api", + "nym-api", "explorer-api", ] -exclude = ["explorer", "contracts", "clients/webassembly", "nym-wallet", "nym-connect"] +exclude = ["explorer", "contracts", "clients/webassembly", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop"] + +[workspace.package] +authors = ["Nym Technologies SA"] +repository = "https://github.com/nymtech/nym" +homepage = "https://nymtech.net" +documentation = "https://nymtech.net" +edition = "2021" +license = "Apache-2.0" + +[workspace.dependencies] +async-trait = "0.1.64" +cfg-if = "1.0.0" +cosmwasm-derive = "=1.0.0" +cosmwasm-schema = "=1.0.0" +cosmwasm-std = "=1.0.0" +cosmwasm-storage = "=1.0.0" +cw-utils = "=0.13.4" +cw-storage-plus = "=0.13.4" +cw2 = { version = "=0.13.4" } +cw3 = { version = "=0.13.4" } +cw3-fixed-multisig = { version = "=0.13.4" } +cw4 = { version = "=0.13.4" } +dotenvy = "0.15.6" +lazy_static = "1.4.0" +log = "0.4" +once_cell = "1.7.2" +rand = "0.8.5" +serde = "1.0.152" +serde_json = "1.0.91" +tap = "1.0.1" +thiserror = "1.0.38" +tokio = "1.24.1" +url = "2.2" diff --git a/Makefile b/Makefile index 8f5aba2678..7a012f7ba2 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,22 @@ test: clippy-all cargo-test wasm fmt +test-no-mobile: clippy-all-no-mobile cargo-test-no-mobile wasm fmt-no-mobile test-all: test cargo-test-expensive +test-all-no-mobile: test-no-mobile cargo-test-expensive no-clippy: build cargo-test wasm fmt +no-clippy-no-mobile: build-no-mobile cargo-test-no-mobile wasm fmt-no-mobile happy: fmt clippy-happy test -clippy-all: clippy-main clippy-coconut clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-wasm-client -clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect -cargo-test: test-main test-contracts test-wallet test-connect test-coconut test-wasm-client -cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive test-coconut-expensive -build: build-contracts build-wallet build-main build-connect build-wasm-client -fmt: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-wasm-client +happy-no-mobile: fmt-no-mobile clippy-happy-no-mobile test-no-mobile +clippy-all: clippy-all-no-mobile clippy-all-connect-mobile +clippy-all-no-mobile: clippy-main clippy-main-examples clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-wasm-client +clippy-happy: clippy-happy-no-mobile clippy-happy-connect-mobile +clippy-happy-no-mobile: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect +cargo-test: cargo-test-no-mobile test-connect-mobile +cargo-test-no-mobile: test-main test-contracts test-wallet test-connect +cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive +build: build-no-mobile build-connect-mobile +build-no-mobile: build-contracts build-wallet build-main build-main-examples build-connect build-wasm-client +fmt: fmt-no-mobile fmt-connect-mobile +fmt-no-mobile: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-wasm-client clippy-happy-main: cargo clippy @@ -19,16 +28,19 @@ clippy-happy-wallet: cargo clippy --manifest-path nym-wallet/Cargo.toml clippy-happy-connect: - cargo clippy --manifest-path nym-connect/Cargo.toml + cargo clippy --manifest-path nym-connect/desktop/Cargo.toml + +clippy-happy-connect-mobile: + cargo clippy --manifest-path nym-connect/mobile/src-tauri/Cargo.toml clippy-main: cargo clippy --workspace -- -D warnings -clippy-coconut: - cargo clippy --workspace --features coconut -- -D warnings +clippy-main-examples: + cargo clippy --workspace --examples -- -D warnings clippy-wasm: - cargo clippy --workspace --features wasm -- -D warnings + cargo clippy --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --workspace -- -D warnings clippy-all-contracts: @@ -38,7 +50,10 @@ clippy-all-wallet: cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings clippy-all-connect: - cargo clippy --workspace --manifest-path nym-connect/Cargo.toml --all-features -- -D warnings + cargo clippy --workspace --manifest-path nym-connect/desktop/Cargo.toml --all-features -- -D warnings + +clippy-all-connect-mobile: + cargo clippy --workspace --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all-features -- -D warnings clippy-all-wasm-client: cargo clippy --workspace --manifest-path clients/webassembly/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings @@ -46,19 +61,9 @@ clippy-all-wasm-client: test-main: cargo test --workspace -test-coconut: - cargo test --workspace --features coconut - -test-wasm: - cargo test --workspace --features wasm - - test-main-expensive: cargo test --workspace -- --ignored -test-coconut-expensive: - cargo test --workspace --features coconut -- --ignored - test-contracts: cargo test --manifest-path contracts/Cargo.toml --all-features @@ -71,18 +76,24 @@ test-wallet: test-wallet-expensive: cargo test --manifest-path nym-wallet/Cargo.toml --all-features -- --ignored -test-wasm-client: - cargo test --workspace --manifest-path clients/webassembly/Cargo.toml --all-features - test-connect: - cargo test --manifest-path nym-connect/Cargo.toml --all-features + cargo test --manifest-path nym-connect/desktop/Cargo.toml --all-features test-connect-expensive: - cargo test --manifest-path nym-connect/Cargo.toml --all-features -- --ignored + cargo test --manifest-path nym-connect/desktop/Cargo.toml --all-features -- --ignored + +test-connect-mobile: + cargo test --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all-features + +test-connect-mobile-expensive: + cargo test --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all-features -- --ignored build-main: cargo build --workspace +build-main-examples: + cargo build --workspace --examples + build-contracts: cargo build --manifest-path contracts/Cargo.toml --workspace @@ -90,7 +101,10 @@ build-wallet: cargo build --manifest-path nym-wallet/Cargo.toml --workspace build-connect: - cargo build --manifest-path nym-connect/Cargo.toml --workspace + cargo build --manifest-path nym-connect/desktop/Cargo.toml --workspace + +build-connect-mobile: + cargo build --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --workspace build-explorer-api: cargo build --manifest-path explorer-api/Cargo.toml --workspace @@ -111,13 +125,18 @@ fmt-wallet: cargo fmt --manifest-path nym-wallet/Cargo.toml --all fmt-connect: - cargo fmt --manifest-path nym-connect/Cargo.toml --all + cargo fmt --manifest-path nym-connect/desktop/Cargo.toml --all + +fmt-connect-mobile: + cargo fmt --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all fmt-wasm-client: cargo fmt --manifest-path clients/webassembly/Cargo.toml --all wasm: RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown + wasm-opt -Os contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm + wasm-opt -Os contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm mixnet-opt: wasm cd contracts/mixnet && make opt @@ -125,3 +144,6 @@ mixnet-opt: wasm generate-typescript: cd tools/ts-rs-cli && cargo run && cd ../.. yarn types:lint:fix + +run-api-tests: + cd nym-api/tests/functional_test && yarn test:qa diff --git a/README.md b/README.md index 96409f6e0e..3a7f37ca33 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr * 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/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop) +[![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) ### Building diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml index 9965849481..36fc8b52eb 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -1,49 +1,57 @@ [package] name = "client-core" -version = "1.1.4" +version = "1.1.13" authors = ["Dave Hrycyszyn "] edition = "2021" +rust-version = "1.66" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait = { version = "0.1.58" } +async-trait = { workspace = true } dirs = "4.0" dashmap = "5.4.0" futures = "0.3" humantime-serde = "1.0" -log = "0.4" +log = { workspace = true } rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.89" tap = "1.0.1" thiserror = "1.0.34" url = { version ="2.2", features = ["serde"] } -tokio = { version = "1.21.2", features = ["macros"]} +tungstenite = { version = "0.13.0", default-features = false } +tokio = { version = "1.24.1", features = ["macros"]} time = "0.3.17" # internal -config = { path = "../../common/config" } -client-connections = { path = "../../common/client-connections" } -crypto = { path = "../../common/crypto" } +nym-config = { path = "../../common/config" } +nym-crypto = { path = "../../common/crypto" } gateway-client = { path = "../../common/client-libs/gateway-client" } #gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] } gateway-requests = { path = "../../gateway/gateway-requests" } -nonexhaustive-delayqueue = { path = "../../common/nonexhaustive-delayqueue" } -nymsphinx = { path = "../../common/nymsphinx" } -pemstore = { path = "../../common/pemstore" } -topology = { path = "../../common/topology" } +nym-nonexhaustive-delayqueue = { path = "../../common/nonexhaustive-delayqueue" } +nym-sphinx = { path = "../../common/nymsphinx" } +nym-pemstore = { path = "../../common/pemstore" } +nym-topology = { path = "../../common/topology" } validator-client = { path = "../../common/client-libs/validator-client", default-features = false } -task = { path = "../../common/task" } +nym-task = { path = "../../common/task" } + +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.validator-client] +path = "../../common/client-libs/validator-client" +features = ["nyxd-client"] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream] -version = "0.1.9" +version = "0.1.11" features = ["time"] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] -version = "1.21.2" +version = "1.24.1" features = ["time"] +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite] +version = "0.14" + [target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx] version = "0.6.2" features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] @@ -65,6 +73,7 @@ features = ["futures"] [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils] path = "../../common/wasm-utils" +features = ["websocket"] [target."cfg(target_arch = \"wasm32\")".dependencies.time] version = "0.3.17" @@ -74,12 +83,11 @@ features = ["wasm-bindgen"] tempfile = "3.1.0" [build-dependencies] -tokio = { version = "1.21.2", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } [features] default = [] fs-surb-storage = ["sqlx"] wasm = ["gateway-client/wasm"] -coconut = ["gateway-client/coconut", "gateway-requests/coconut"] diff --git a/clients/client-core/build.rs b/clients/client-core/build.rs index bac1b20c53..62c02c3968 100644 --- a/clients/client-core/build.rs +++ b/clients/client-core/build.rs @@ -9,9 +9,9 @@ async fn main() { use std::env; let out_dir = env::var("OUT_DIR").unwrap(); - let database_path = format!("{}/fs-surbs-example.sqlite", out_dir); + let database_path = format!("{out_dir}/fs-surbs-example.sqlite"); - let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path)) + let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc")) .await .expect("Failed to create SQLx database connection"); diff --git a/clients/client-core/src/client/base_client/helpers.rs b/clients/client-core/src/client/base_client/helpers.rs new file mode 100644 index 0000000000..21485420bc --- /dev/null +++ b/clients/client-core/src/client/base_client/helpers.rs @@ -0,0 +1,11 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 +// +use crate::{client::replies::reply_storage, config::DebugConfig}; + +pub fn setup_empty_reply_surb_backend(debug_config: &DebugConfig) -> reply_storage::Empty { + reply_storage::Empty { + min_surb_threshold: debug_config.minimum_reply_surb_storage_threshold, + max_surb_threshold: debug_config.maximum_reply_surb_storage_threshold, + } +} diff --git a/clients/client-core/src/client/base_client/mod.rs b/clients/client-core/src/client/base_client/mod.rs index 932fb77b31..e8a3d215f9 100644 --- a/clients/client-core/src/client/base_client/mod.rs +++ b/clients/client-core/src/client/base_client/mod.rs @@ -15,43 +15,87 @@ use crate::client::replies::reply_controller::{ReplyControllerReceiver, ReplyCon use crate::client::replies::reply_storage::{ CombinedReplyStorage, PersistentReplyStorage, ReplyStorageBackend, SentReplyKeys, }; +use crate::client::topology_control::nym_api_provider::NymApiTopologyProvider; use crate::client::topology_control::{ TopologyAccessor, TopologyRefresher, TopologyRefresherConfig, }; use crate::config::{Config, DebugConfig, GatewayEndpointConfig}; use crate::error::ClientCoreError; use crate::spawn_future; -use client_connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths}; -use crypto::asymmetric::{encryption, identity}; use futures::channel::mpsc; use gateway_client::bandwidth::BandwidthController; +#[cfg(target_arch = "wasm32")] +use gateway_client::wasm_mockups::CosmWasmClient; use gateway_client::{ AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver, MixnetMessageSender, }; use log::{debug, info}; -use nymsphinx::acknowledgements::AckKey; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::addressing::nodes::NodeIdentity; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_sphinx::acknowledgements::AckKey; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::addressing::nodes::NodeIdentity; +use nym_sphinx::receiver::ReconstructedMessage; +use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths}; +use nym_task::{TaskClient, TaskManager}; +use nym_topology::provider_trait::TopologyProvider; use std::sync::Arc; use std::time::Duration; use tap::TapFallible; -use task::{ShutdownListener, ShutdownNotifier}; use url::Url; +#[cfg(not(target_arch = "wasm32"))] +use validator_client::nyxd::CosmWasmClient; + +use super::received_buffer::ReceivedBufferMessage; + #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] pub mod non_wasm_helpers; +pub mod helpers; + +#[derive(Clone)] pub struct ClientInput { - pub shared_lane_queue_lengths: LaneQueueLengths, pub connection_command_sender: ConnectionCommandSender, pub input_sender: InputMessageSender, } +impl ClientInput { + pub async fn send( + &self, + message: InputMessage, + ) -> Result<(), tokio::sync::mpsc::error::SendError> { + self.input_sender.send(message).await + } +} + +#[derive(Clone)] pub struct ClientOutput { pub received_buffer_request_sender: ReceivedBufferRequestSender, } +impl ClientOutput { + pub fn register_receiver( + &mut self, + ) -> Result>, ClientCoreError> { + let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded(); + + self.received_buffer_request_sender + .unbounded_send(ReceivedBufferMessage::ReceiverAnnounce( + reconstructed_sender, + )) + .map_err(|_| ClientCoreError::FailedToRegisterReceiver)?; + + Ok(reconstructed_receiver) + } +} + +pub struct ClientState { + pub shared_lane_queue_lengths: LaneQueueLengths, + pub reply_controller_sender: ReplyControllerSender, + pub topology_accessor: TopologyAccessor, +} + pub enum ClientInputStatus { AwaitingProducer { client_input: ClientInput }, Connected, @@ -80,36 +124,65 @@ impl ClientOutputStatus { } } -pub struct BaseClientBuilder<'a, B> { +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum CredentialsToggle { + Enabled, + Disabled, +} + +impl CredentialsToggle { + pub fn is_enabled(&self) -> bool { + self == &CredentialsToggle::Enabled + } + + pub fn is_disabled(&self) -> bool { + self == &CredentialsToggle::Disabled + } +} + +impl From for CredentialsToggle { + fn from(value: bool) -> Self { + if value { + CredentialsToggle::Enabled + } else { + CredentialsToggle::Disabled + } + } +} + +pub struct BaseClientBuilder<'a, B, C: Clone> { // due to wasm limitations I had to split it like this : ( gateway_config: &'a GatewayEndpointConfig, debug_config: &'a DebugConfig, disabled_credentials: bool, - validator_api_endpoints: Vec, + nym_api_endpoints: Vec, reply_storage_backend: B, - bandwidth_controller: Option, + custom_topology_provider: Option>, + bandwidth_controller: Option>, key_manager: KeyManager, } -impl<'a, B> BaseClientBuilder<'a, B> +impl<'a, B, C> BaseClientBuilder<'a, B, C> where B: ReplyStorageBackend + Send + Sync + 'static, + C: CosmWasmClient + Sync + Send + Clone + 'static, { pub fn new_from_base_config( base_config: &'a Config, key_manager: KeyManager, - bandwidth_controller: Option, + bandwidth_controller: Option>, reply_storage_backend: B, - ) -> BaseClientBuilder<'a, B> { + ) -> BaseClientBuilder<'a, B, C> { BaseClientBuilder { gateway_config: base_config.get_gateway_endpoint_config(), debug_config: base_config.get_debug_config(), disabled_credentials: base_config.get_disabled_credentials_mode(), - validator_api_endpoints: base_config.get_validator_api_endpoints(), + nym_api_endpoints: base_config.get_nym_api_endpoints(), bandwidth_controller, reply_storage_backend, key_manager, + custom_topology_provider: None, } } @@ -117,22 +190,28 @@ where gateway_config: &'a GatewayEndpointConfig, debug_config: &'a DebugConfig, key_manager: KeyManager, - bandwidth_controller: Option, + bandwidth_controller: Option>, reply_storage_backend: B, - disabled_credentials: bool, - validator_api_endpoints: Vec, - ) -> BaseClientBuilder<'a, B> { + credentials_toggle: CredentialsToggle, + nym_api_endpoints: Vec, + ) -> BaseClientBuilder<'a, B, C> { BaseClientBuilder { gateway_config, debug_config, - disabled_credentials, - validator_api_endpoints, - bandwidth_controller, + disabled_credentials: credentials_toggle.is_disabled(), + nym_api_endpoints, reply_storage_backend, + custom_topology_provider: None, + bandwidth_controller, key_manager, } } + pub fn with_topology_provider(mut self, provider: Box) -> Self { + self.custom_topology_provider = Some(provider); + self + } + pub fn as_mix_recipient(&self) -> Recipient { Recipient::new( *self.key_manager.identity_keypair().public_key(), @@ -151,7 +230,7 @@ where self_address: Recipient, topology_accessor: TopologyAccessor, mix_tx: BatchMixMessageSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { info!("Starting loop cover traffic stream..."); @@ -185,7 +264,7 @@ where reply_controller_receiver: ReplyControllerReceiver, lane_queue_lengths: LaneQueueLengths, client_connection_rx: ConnectionCommandReceiver, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { info!("Starting real traffic stream..."); @@ -212,7 +291,7 @@ where mixnet_receiver: MixnetMessageReceiver, reply_key_storage: SentReplyKeys, reply_controller_sender: ReplyControllerSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { info!("Starting received messages buffer controller..."); ReceivedMessagesBufferController::new( @@ -229,28 +308,25 @@ where &mut self, mixnet_message_sender: MixnetMessageSender, ack_sender: AcknowledgementSender, - shutdown: ShutdownListener, - ) -> Result> { + shutdown: TaskClient, + ) -> Result, ClientCoreError> { let gateway_id = self.gateway_config.gateway_id.clone(); if gateway_id.is_empty() { return Err(ClientCoreError::GatewayIdUnknown); } - let gateway_owner = self.gateway_config.gateway_owner.clone(); - if gateway_owner.is_empty() { - return Err(ClientCoreError::GatewayOwnerUnknown); - } let gateway_address = self.gateway_config.gateway_listener.clone(); if gateway_address.is_empty() { - return Err(ClientCoreError::GatwayAddressUnknown); + return Err(ClientCoreError::GatewayAddressUnknown); } let gateway_identity = identity::PublicKey::from_base58_string(gateway_id) .map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?; // disgusting wasm workaround since there's no key persistence there (nor `client init`) - let shared_key = if self.key_manager.gateway_key_set() { + let shared_key = if self.key_manager.is_gateway_key_set() { Some(self.key_manager.gateway_shared_key()) } else { + log::info!("Gateway key not set! Will proceed anyway."); None }; @@ -258,7 +334,6 @@ where gateway_address, self.key_manager.identity_keypair(), gateway_identity, - gateway_owner, shared_key, mixnet_message_sender, ack_sender, @@ -278,25 +353,38 @@ where Ok(gateway_client) } + fn setup_topology_provider( + custom_provider: Option>, + nym_api_urls: Vec, + ) -> Box { + // if no custom provider was ... provided ..., create one using nym-api + custom_provider.unwrap_or_else(|| { + Box::new(NymApiTopologyProvider::new( + nym_api_urls, + env!("CARGO_PKG_VERSION").to_string(), + )) + }) + } + // future responsible for periodically polling directory server and updating // the current global view of topology async fn start_topology_refresher( - validator_api_urls: Vec, + topology_provider: Box, refresh_rate: Duration, topology_accessor: TopologyAccessor, - shutdown: ShutdownListener, - ) -> Result<(), ClientCoreError> { - let topology_refresher_config = TopologyRefresherConfig::new( - validator_api_urls, - refresh_rate, - env!("CARGO_PKG_VERSION").to_string(), + shutdown: TaskClient, + ) -> Result<(), ClientCoreError> { + let topology_refresher_config = TopologyRefresherConfig::new(refresh_rate); + + let mut topology_refresher = TopologyRefresher::new( + topology_refresher_config, + topology_accessor, + topology_provider, ); - let mut topology_refresher = - TopologyRefresher::new(topology_refresher_config, topology_accessor); // before returning, block entire runtime to refresh the current network view so that any // components depending on topology would see a non-empty view info!("Obtaining initial network topology"); - topology_refresher.refresh().await; + topology_refresher.try_refresh().await; if let Err(err) = topology_refresher.ensure_topology_is_routable().await { log::error!( @@ -316,8 +404,8 @@ where // over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for // requests? fn start_mix_traffic_controller( - gateway_client: GatewayClient, - shutdown: ShutdownListener, + gateway_client: GatewayClient, + shutdown: TaskClient, ) -> BatchMixMessageSender { info!("Starting mix traffic controller..."); let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client); @@ -327,25 +415,43 @@ where async fn setup_persistent_reply_storage( backend: B, - shutdown: ShutdownListener, - ) -> Result> { - let persistent_storage = PersistentReplyStorage::new(backend); - let mem_store = persistent_storage - .load_state_from_backend() - .await - .map_err(|err| ClientCoreError::SurbStorageError { source: err })?; - - let store_clone = mem_store.clone(); - spawn_future(async move { - persistent_storage - .flush_on_shutdown(store_clone, shutdown) + shutdown: TaskClient, + ) -> Result + where + ::StorageError: Sync + Send, + { + if backend.is_active() { + log::trace!("Setup persistent reply storage"); + let persistent_storage = PersistentReplyStorage::new(backend); + let mem_store = persistent_storage + .load_state_from_backend() .await - }); + .map_err(|err| ClientCoreError::SurbStorageError { + source: Box::new(err), + })?; - Ok(mem_store) + let store_clone = mem_store.clone(); + spawn_future(async move { + persistent_storage + .flush_on_shutdown(store_clone, shutdown) + .await + }); + + Ok(mem_store) + } else { + log::trace!("Setup inactive reply storage"); + Ok(backend + .get_inactive_storage() + .map_err(|err| ClientCoreError::SurbStorageError { + source: Box::new(err), + })?) + } } - pub async fn start_base(mut self) -> Result> { + pub async fn start_base(mut self) -> Result + where + ::StorageError: Sync + Send, + { info!("Starting nym client"); // channels for inter-component communication // TODO: make the channels be internally created by the relevant components @@ -367,29 +473,35 @@ where let shared_topology_accessor = TopologyAccessor::new(); // Shutdown notifier for signalling tasks to stop - let shutdown = ShutdownNotifier::default(); + let task_manager = TaskManager::default(); // channels responsible for dealing with reply-related fun let (reply_controller_sender, reply_controller_receiver) = - reply_controller::new_control_channels(); + reply_controller::requests::new_control_channels(); let self_address = self.as_mix_recipient(); // the components are started in very specific order. Unless you know what you are doing, // do not change that. let gateway_client = self - .start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe()) + .start_gateway_client(mixnet_messages_sender, ack_sender, task_manager.subscribe()) .await?; - let reply_storage = - Self::setup_persistent_reply_storage(self.reply_storage_backend, shutdown.subscribe()) - .await?; + let reply_storage = Self::setup_persistent_reply_storage( + self.reply_storage_backend, + task_manager.subscribe(), + ) + .await?; + let topology_provider = Self::setup_topology_provider( + self.custom_topology_provider.take(), + self.nym_api_endpoints, + ); Self::start_topology_refresher( - self.validator_api_endpoints.clone(), + topology_provider, self.debug_config.topology_refresh_rate, shared_topology_accessor.clone(), - shutdown.subscribe(), + task_manager.subscribe(), ) .await?; @@ -399,7 +511,7 @@ where mixnet_messages_receiver, reply_storage.key_storage(), reply_controller_sender.clone(), - shutdown.subscribe(), + task_manager.subscribe(), ); // The sphinx_message_sender is the transmitter for any component generating sphinx packets @@ -407,7 +519,7 @@ where // traffic stream. // The MixTrafficController then sends the actual traffic let sphinx_message_sender = - Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe()); + Self::start_mix_traffic_controller(gateway_client, task_manager.subscribe()); // Channels that the websocket listener can use to signal downstream to the real traffic // controller that connections are closed. @@ -435,11 +547,11 @@ where input_receiver, sphinx_message_sender.clone(), reply_storage, - reply_controller_sender, + reply_controller_sender.clone(), reply_controller_receiver, shared_lane_queue_lengths.clone(), client_connection_rx, - shutdown.subscribe(), + task_manager.subscribe(), ); if !self.debug_config.disable_loop_cover_traffic_stream { @@ -447,9 +559,9 @@ where self.debug_config, self.key_manager.ack_key(), self_address, - shared_topology_accessor, + shared_topology_accessor.clone(), sphinx_message_sender, - shutdown.subscribe(), + task_manager.subscribe(), ); } @@ -459,7 +571,6 @@ where Ok(BaseClient { client_input: ClientInputStatus::AwaitingProducer { client_input: ClientInput { - shared_lane_queue_lengths, connection_command_sender: client_connection_tx, input_sender, }, @@ -469,7 +580,12 @@ where received_buffer_request_sender, }, }, - shutdown_notifier: shutdown, + client_state: ClientState { + shared_lane_queue_lengths, + reply_controller_sender, + topology_accessor: shared_topology_accessor, + }, + task_manager, }) } } @@ -477,6 +593,7 @@ where pub struct BaseClient { pub client_input: ClientInputStatus, pub client_output: ClientOutputStatus, + pub client_state: ClientState, - pub shutdown_notifier: ShutdownNotifier, + pub task_manager: TaskManager, } diff --git a/clients/client-core/src/client/base_client/non_wasm_helpers.rs b/clients/client-core/src/client/base_client/non_wasm_helpers.rs index 2d710052b1..9dde32f9d2 100644 --- a/clients/client-core/src/client/base_client/non_wasm_helpers.rs +++ b/clients/client-core/src/client/base_client/non_wasm_helpers.rs @@ -14,13 +14,15 @@ use time::OffsetDateTime; async fn setup_fresh_backend>( db_path: P, debug_config: &DebugConfig, -) -> Result> { +) -> Result { info!("creating fresh surb database"); let mut storage_backend = match fs_backend::Backend::init(db_path).await { Ok(backend) => backend, Err(err) => { error!("failed to setup persistent storage backend for our reply needs: {err}"); - return Err(ClientCoreError::SurbStorageError { source: err }); + return Err(ClientCoreError::SurbStorageError { + source: Box::new(err), + }); } }; @@ -34,11 +36,21 @@ async fn setup_fresh_backend>( storage_backend .init_fresh(&mem_store) .await - .map_err(|err| ClientCoreError::SurbStorageError { source: err })?; + .map_err(|err| ClientCoreError::SurbStorageError { + source: Box::new(err), + })?; Ok(storage_backend) } +fn setup_inactive_backend(debug_config: &DebugConfig) -> fs_backend::Backend { + info!("creating inactive surb database"); + fs_backend::Backend::new_inactive( + debug_config.minimum_reply_surb_storage_threshold, + debug_config.maximum_reply_surb_storage_threshold, + ) +} + fn archive_corrupted_database>(db_path: P) -> io::Result<()> { let db_path = db_path.as_ref(); debug_assert!(db_path.exists()); @@ -49,7 +61,7 @@ fn archive_corrupted_database>(db_path: P) -> io::Result<()> { let new_extension = if let Some(existing_extension) = db_path.extension().and_then(|ext| ext.to_str()) { - format!("{existing_extension}.{}", suffix) + format!("{existing_extension}.{suffix}") } else { suffix }; @@ -61,23 +73,28 @@ fn archive_corrupted_database>(db_path: P) -> io::Result<()> { } pub async fn setup_fs_reply_surb_backend>( - db_path: P, + db_path: Option

, debug_config: &DebugConfig, -) -> Result> { - // if the database file doesnt exist, initialise fresh storage, otherwise attempt to load the existing one - let db_path = db_path.as_ref(); - if db_path.exists() { - info!("loading existing surb database"); - match fs_backend::Backend::try_load(db_path).await { - Ok(backend) => Ok(backend), - Err(err) => { - error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future"); +) -> Result { + if let Some(db_path) = db_path { + // if the database file doesnt exist, initialise fresh storage, otherwise attempt to load + // the existing one + let db_path = db_path.as_ref(); + if db_path.exists() { + info!("loading existing surb database"); + match fs_backend::Backend::try_load(db_path).await { + Ok(backend) => Ok(backend), + Err(err) => { + error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future"); - archive_corrupted_database(db_path)?; - setup_fresh_backend(db_path, debug_config).await + archive_corrupted_database(db_path)?; + setup_fresh_backend(db_path, debug_config).await + } } + } else { + setup_fresh_backend(db_path, debug_config).await } } else { - setup_fresh_backend(db_path, debug_config).await + Ok(setup_inactive_backend(debug_config)) } } diff --git a/clients/client-core/src/client/cover_traffic_stream.rs b/clients/client-core/src/client/cover_traffic_stream.rs index e3fd0ca197..eeb0652bf5 100644 --- a/clients/client-core/src/client/cover_traffic_stream.rs +++ b/clients/client-core/src/client/cover_traffic_stream.rs @@ -7,11 +7,11 @@ use crate::spawn_future; use futures::task::{Context, Poll}; use futures::{Future, Stream, StreamExt}; use log::*; -use nymsphinx::acknowledgements::AckKey; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::cover::generate_loop_cover_packet; -use nymsphinx::params::PacketSize; -use nymsphinx::utils::sample_poisson_duration; +use nym_sphinx::acknowledgements::AckKey; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::cover::generate_loop_cover_packet; +use nym_sphinx::params::PacketSize; +use nym_sphinx::utils::sample_poisson_duration; use rand::{rngs::OsRng, CryptoRng, Rng}; use std::pin::Pin; use std::sync::Arc; @@ -192,7 +192,7 @@ impl LoopCoverTrafficStream { // However it's still useful to alert the user that the gateway or the link to // the gateway can't keep up. Either due to insufficient bandwidth on the // client side, or that the gateway is overloaded. - log::warn!("Failed to send sphinx packet - gateway or connection to gatway can't keep up"); + log::warn!("Failed to send sphinx packet - gateway or connection to gateway can't keep up"); } TrySendError::Closed(_) => { log::warn!("Failed to send cover message - channel closed"); @@ -213,7 +213,7 @@ impl LoopCoverTrafficStream { tokio::task::yield_now().await; } - pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) { + pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) { // we should set initial delay only when we actually start the stream let sampled = sample_poisson_duration(&mut self.rng, self.average_cover_message_sending_delay); diff --git a/clients/client-core/src/client/helpers/mod.rs b/clients/client-core/src/client/helpers/mod.rs new file mode 100644 index 0000000000..ffffea0313 --- /dev/null +++ b/clients/client-core/src/client/helpers/mod.rs @@ -0,0 +1,14 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(not(target_arch = "wasm32"))] +mod non_wasm; + +#[cfg(target_arch = "wasm32")] +mod wasm; + +#[cfg(not(target_arch = "wasm32"))] +pub use non_wasm::*; + +#[cfg(target_arch = "wasm32")] +pub use wasm::*; diff --git a/clients/client-core/src/client/helpers/non_wasm.rs b/clients/client-core/src/client/helpers/non_wasm.rs new file mode 100644 index 0000000000..ff5c9c0047 --- /dev/null +++ b/clients/client-core/src/client/helpers/non_wasm.rs @@ -0,0 +1,13 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub use tokio::time::*; +pub type IntervalStream = tokio_stream::wrappers::IntervalStream; + +pub(crate) fn get_time_now() -> Instant { + Instant::now() +} + +pub(crate) fn new_interval_stream(polling_rate: Duration) -> IntervalStream { + tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(polling_rate)) +} diff --git a/clients/client-core/src/client/helpers/wasm.rs b/clients/client-core/src/client/helpers/wasm.rs new file mode 100644 index 0000000000..f04dd64028 --- /dev/null +++ b/clients/client-core/src/client/helpers/wasm.rs @@ -0,0 +1,16 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::time::Duration; +use wasm_timer; + +pub use wasm_timer::*; +pub type IntervalStream = gloo_timers::future::IntervalStream; + +pub(crate) fn get_time_now() -> Instant { + wasm_timer::Instant::now() +} + +pub(crate) fn new_interval_stream(polling_rate: Duration) -> IntervalStream { + gloo_timers::future::IntervalStream::new(polling_rate.as_millis() as u32) +} diff --git a/clients/client-core/src/client/inbound_messages.rs b/clients/client-core/src/client/inbound_messages.rs index 890787aa60..69f8af0d45 100644 --- a/clients/client-core/src/client/inbound_messages.rs +++ b/clients/client-core/src/client/inbound_messages.rs @@ -1,6 +1,6 @@ -use client_connections::TransmissionLane; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_task::connections::TransmissionLane; pub type InputMessageSender = tokio::sync::mpsc::Sender; pub type InputMessageReceiver = tokio::sync::mpsc::Receiver; @@ -77,4 +77,12 @@ impl InputMessage { lane, } } + + pub fn lane(&self) -> &TransmissionLane { + match self { + InputMessage::Regular { lane, .. } + | InputMessage::Anonymous { lane, .. } + | InputMessage::Reply { lane, .. } => lane, + } + } } diff --git a/clients/client-core/src/client/key_manager.rs b/clients/client-core/src/client/key_manager.rs index 6fa264f43f..4f0f29eb8b 100644 --- a/clients/client-core/src/client/key_manager.rs +++ b/clients/client-core/src/client/key_manager.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::persistence::key_pathfinder::ClientKeyPathfinder; -use crypto::asymmetric::{encryption, identity}; use gateway_requests::registration::handshake::SharedKeys; use log::*; -use nymsphinx::acknowledgements::AckKey; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_sphinx::acknowledgements::AckKey; use rand::{CryptoRng, RngCore}; use std::io; use std::sync::Arc; @@ -17,6 +17,7 @@ use std::sync::Arc; // use the old key after new one was issued. // Remember that Arc has Deref implementation for T +#[derive(Clone)] pub struct KeyManager { /// identity key associated with the client instance. identity_keypair: Arc, @@ -41,9 +42,6 @@ pub struct KeyManager { */ impl KeyManager { - // this is actually **NOT** dead code - // I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute - #[allow(dead_code)] /// Creates new instance of a [`KeyManager`] pub fn new(rng: &mut R) -> Self where @@ -57,87 +55,161 @@ impl KeyManager { } } - // this is actually **NOT** dead code - // I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute - #[allow(dead_code)] - /// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`]. - pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc) { - self.gateway_shared_key = Some(gateway_shared_key) + pub fn from_keys( + id_keypair: identity::KeyPair, + enc_keypair: encryption::KeyPair, + gateway_shared_key: SharedKeys, + ack_key: AckKey, + ) -> Self { + Self { + identity_keypair: Arc::new(id_keypair), + encryption_keypair: Arc::new(enc_keypair), + gateway_shared_key: Some(Arc::new(gateway_shared_key)), + ack_key: Arc::new(ack_key), + } } - /// Loads previously stored keys from the disk. - pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result { + /// Loads previously stored client keys from the disk. + fn load_client_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result { let identity_keypair: identity::KeyPair = - pemstore::load_keypair(&pemstore::KeyPairPath::new( + nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( client_pathfinder.private_identity_key().to_owned(), client_pathfinder.public_identity_key().to_owned(), ))?; let encryption_keypair: encryption::KeyPair = - pemstore::load_keypair(&pemstore::KeyPairPath::new( + nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( client_pathfinder.private_encryption_key().to_owned(), client_pathfinder.public_encryption_key().to_owned(), ))?; - let gateway_shared_key: SharedKeys = - pemstore::load_key(client_pathfinder.gateway_shared_key())?; + let ack_key: AckKey = nym_pemstore::load_key(client_pathfinder.ack_key())?; - let ack_key: AckKey = pemstore::load_key(client_pathfinder.ack_key())?; - - // TODO: ack key is never stored so it is generated now. But perhaps it should be stored - // after all for consistency sake? Ok(KeyManager { identity_keypair: Arc::new(identity_keypair), encryption_keypair: Arc::new(encryption_keypair), - gateway_shared_key: Some(Arc::new(gateway_shared_key)), + gateway_shared_key: None, ack_key: Arc::new(ack_key), }) } - // this is actually **NOT** dead code - // I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute - #[allow(dead_code)] + /// Loads previously stored keys from the disk. Fails if not all, including the shared gateway + /// key, is available. + pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result { + let mut key_manager = Self::load_client_keys(client_pathfinder)?; + + let gateway_shared_key: SharedKeys = + nym_pemstore::load_key(client_pathfinder.gateway_shared_key())?; + + key_manager.gateway_shared_key = Some(Arc::new(gateway_shared_key)); + + Ok(key_manager) + } + + /// Loads previously stored keys from the disk. Fails if client keys are not availabe, but the + /// shared gateway key is optional. + pub fn load_keys_but_gateway_is_optional( + client_pathfinder: &ClientKeyPathfinder, + ) -> io::Result { + let mut key_manager = Self::load_client_keys(client_pathfinder)?; + + let gateway_shared_key: Result = + nym_pemstore::load_key(client_pathfinder.gateway_shared_key()); + + // It's ok if the gateway key was not found + let gateway_shared_key = match gateway_shared_key { + Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(err), + Ok(key) => Ok(Some(key)), + }?; + + key_manager.gateway_shared_key = gateway_shared_key.map(Arc::new); + + Ok(key_manager) + } + /// Stores all available keys on the disk. // While perhaps there is no much point in storing the `AckKey` on the disk, // it is done so for the consistency sake so that you wouldn't require an rng instance // during `load_keys` to generate the said key. pub fn store_keys(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> { - pemstore::store_keypair( + nym_pemstore::store_keypair( self.identity_keypair.as_ref(), - &pemstore::KeyPairPath::new( + &nym_pemstore::KeyPairPath::new( client_pathfinder.private_identity_key().to_owned(), client_pathfinder.public_identity_key().to_owned(), ), )?; - pemstore::store_keypair( + nym_pemstore::store_keypair( self.encryption_keypair.as_ref(), - &pemstore::KeyPairPath::new( + &nym_pemstore::KeyPairPath::new( client_pathfinder.private_encryption_key().to_owned(), client_pathfinder.public_encryption_key().to_owned(), ), )?; - pemstore::store_key(self.ack_key.as_ref(), client_pathfinder.ack_key())?; + nym_pemstore::store_key(self.ack_key.as_ref(), client_pathfinder.ack_key())?; match self.gateway_shared_key.as_ref() { - None => warn!("No gateway shared key available to store!"), + None => debug!("No gateway shared key available to store!"), Some(gate_key) => { - pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())? + nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())? } } Ok(()) } + pub fn store_gateway_key(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> { + match self.gateway_shared_key.as_ref() { + None => { + return Err(io::Error::new( + io::ErrorKind::Other, + "trying to store a non-existing key", + )) + } + Some(gate_key) => { + nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())? + } + } + + Ok(()) + } + + /// Overwrite the existing identity keypair + pub fn set_identity_keypair(&mut self, id_keypair: identity::KeyPair) { + self.identity_keypair = Arc::new(id_keypair); + } + /// Gets an atomically reference counted pointer to [`identity::KeyPair`]. pub fn identity_keypair(&self) -> Arc { Arc::clone(&self.identity_keypair) } + /// Overwrite the existing encryption keypair + pub fn set_encryption_keypair(&mut self, enc_keypair: encryption::KeyPair) { + self.encryption_keypair = Arc::new(enc_keypair); + } + /// Gets an atomically reference counted pointer to [`encryption::KeyPair`]. pub fn encryption_keypair(&self) -> Arc { Arc::clone(&self.encryption_keypair) } + /// Overwrite the existing ack key + pub fn set_ack_key(&mut self, ack_key: AckKey) { + self.ack_key = Arc::new(ack_key); + } + + /// Gets an atomically reference counted pointer to [`AckKey`]. + pub fn ack_key(&self) -> Arc { + Arc::clone(&self.ack_key) + } + + /// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`]. + pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc) { + self.gateway_shared_key = Some(gateway_shared_key) + } + /// Gets an atomically reference counted pointer to [`SharedKey`]. // since this function is not fully public, it is not expected to be used externally and // hence it's up to us to ensure it's called in correct context @@ -149,12 +221,7 @@ impl KeyManager { ) } - pub fn gateway_key_set(&self) -> bool { + pub fn is_gateway_key_set(&self) -> bool { self.gateway_shared_key.is_some() } - - /// Gets an atomically reference counted pointer to [`AckKey`]. - pub fn ack_key(&self) -> Arc { - Arc::clone(&self.ack_key) - } } diff --git a/clients/client-core/src/client/mix_traffic.rs b/clients/client-core/src/client/mix_traffic.rs index f61a83af95..e4b5024d62 100644 --- a/clients/client-core/src/client/mix_traffic.rs +++ b/clients/client-core/src/client/mix_traffic.rs @@ -2,9 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use crate::spawn_future; +#[cfg(target_arch = "wasm32")] +use gateway_client::wasm_mockups::CosmWasmClient; use gateway_client::GatewayClient; use log::*; -use nymsphinx::forwarding::packet::MixPacket; +use nym_sphinx::forwarding::packet::MixPacket; +#[cfg(not(target_arch = "wasm32"))] +use validator_client::nyxd::CosmWasmClient; pub type BatchMixMessageSender = tokio::sync::mpsc::Sender>; pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver>; @@ -13,10 +17,10 @@ pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver>; pub const MIX_MESSAGE_RECEIVER_BUFFER_SIZE: usize = 32; const MAX_FAILURE_COUNT: usize = 100; -pub struct MixTrafficController { +pub struct MixTrafficController { // TODO: most likely to be replaced by some higher level construct as // later on gateway_client will need to be accessible by other entities - gateway_client: GatewayClient, + gateway_client: GatewayClient, mix_rx: BatchMixMessageReceiver, // TODO: this is temporary work-around. @@ -24,8 +28,13 @@ pub struct MixTrafficController { consecutive_gateway_failure_count: usize, } -impl MixTrafficController { - pub fn new(gateway_client: GatewayClient) -> (MixTrafficController, BatchMixMessageSender) { +impl MixTrafficController +where + C: CosmWasmClient + Sync + Send + Clone + 'static, +{ + pub fn new( + gateway_client: GatewayClient, + ) -> (MixTrafficController, BatchMixMessageSender) { let (sphinx_message_sender, sphinx_message_receiver) = tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE); ( @@ -51,13 +60,13 @@ impl MixTrafficController { }; match result { - Err(e) => { - error!("Failed to send sphinx packet(s) to the gateway! - {:?}", e); + Err(err) => { + error!("Failed to send sphinx packet(s) to the gateway! - {err}"); self.consecutive_gateway_failure_count += 1; if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT { // todo: in the future this should initiate a 'graceful' shutdown or try // to reconnect? - panic!("failed to send sphinx packet to the gateway {} times in a row - assuming the gateway is dead. Can't do anything about it yet :(", MAX_FAILURE_COUNT) + panic!("failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead. Can't do anything about it yet :(") } } Ok(_) => { @@ -67,11 +76,11 @@ impl MixTrafficController { } } - pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) { + pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) { spawn_future(async move { debug!("Started MixTrafficController with graceful shutdown support"); - while !shutdown.is_shutdown() { + loop { tokio::select! { mix_packets = self.mix_rx.recv() => match mix_packets { Some(mix_packets) => { @@ -82,8 +91,9 @@ impl MixTrafficController { break; } }, - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("MixTrafficController: Received shutdown"); + break; } } } diff --git a/clients/client-core/src/client/mod.rs b/clients/client-core/src/client/mod.rs index 8110d52909..789dd300de 100644 --- a/clients/client-core/src/client/mod.rs +++ b/clients/client-core/src/client/mod.rs @@ -3,6 +3,7 @@ pub mod base_client; pub mod cover_traffic_stream; +pub(crate) mod helpers; pub mod inbound_messages; pub mod key_manager; pub mod mix_traffic; @@ -10,3 +11,4 @@ pub mod real_messages_control; pub mod received_buffer; pub mod replies; pub mod topology_control; +pub(crate) mod transmission_buffer; diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs index 95c431fd59..2be5f58f41 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs @@ -5,7 +5,7 @@ use super::action_controller::{AckActionSender, Action}; use futures::StreamExt; use gateway_client::AcknowledgementReceiver; use log::*; -use nymsphinx::{ +use nym_sphinx::{ acknowledgements::{identifier::recover_identifier, AckKey}, chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID}, }; @@ -65,7 +65,7 @@ impl AcknowledgementListener { } } - pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { + pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) { debug!("Started AcknowledgementListener with graceful shutdown support"); while !shutdown.is_shutdown() { @@ -77,7 +77,7 @@ impl AcknowledgementListener { break; } }, - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("AcknowledgementListener: Received shutdown"); } } diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs index cb2e0cc7ca..95fd31ec01 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs @@ -6,9 +6,9 @@ use crate::client::real_messages_control::acknowledgement_control::Retransmissio use futures::channel::mpsc; use futures::StreamExt; use log::*; -use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey}; -use nymsphinx::chunking::fragment::FragmentIdentifier; -use nymsphinx::Delay as SphinxDelay; +use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey}; +use nym_sphinx::chunking::fragment::FragmentIdentifier; +use nym_sphinx::Delay as SphinxDelay; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -213,7 +213,11 @@ impl ActionController { } // note: when the entry expires it's automatically removed from pending_acks_timers - fn handle_expired_ack_timer(&mut self, expired_ack: Expired) { + fn handle_expired_ack_timer( + &mut self, + expired_ack: Expired, + task_client: &mut nym_task::TaskClient, + ) { // I'm honestly not sure how to handle it, because getting it means other things in our // system are already misbehaving. If we ever see this panic, then I guess we should worry // about it. Perhaps just reschedule it at later point? @@ -231,9 +235,16 @@ impl ActionController { // downgrading an arc and then upgrading vs cloning is difference of 30ns vs 15ns // so it's literally a NO difference while it might prevent us from unnecessarily // resending data (in maybe 1 in 1 million cases, but it's something) - self.retransmission_sender + if self + .retransmission_sender .unbounded_send(Arc::downgrade(pending_ack_data)) - .unwrap() + .is_err() + { + assert!( + task_client.is_shutdown_poll(), + "Failed to send pending ack for retransmission" + ); + } } else { // this shouldn't cause any issues but shouldn't have happened to begin with! error!("An already removed pending ack has expired") @@ -249,7 +260,7 @@ impl ActionController { } } - pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { + 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() { @@ -264,21 +275,18 @@ impl ActionController { } }, expired_ack = self.pending_acks_timers.next() => match expired_ack { - Some(expired_ack) => self.handle_expired_ack_timer(expired_ack), + Some(expired_ack) => self.handle_expired_ack_timer(expired_ack, &mut shutdown), None => { log::trace!("ActionController: Stopping since ack channel closed"); break; } }, - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("ActionController: Received shutdown"); } } } - #[cfg(not(target_arch = "wasm32"))] - tokio::time::timeout(Duration::from_secs(5), shutdown.recv()) - .await - .expect("Task stopped without shutdown called"); + shutdown.recv_timeout().await; log::debug!("ActionController: Exiting"); } } diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs index cff6b6d719..ae10c88015 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs @@ -4,10 +4,10 @@ use crate::client::inbound_messages::{InputMessage, InputMessageReceiver}; use crate::client::real_messages_control::message_handler::MessageHandler; use crate::client::replies::reply_controller::ReplyControllerSender; -use client_connections::TransmissionLane; use log::*; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_task::connections::TransmissionLane; use rand::{CryptoRng, Rng}; /// Module responsible for dealing with the received messages: splitting them, creating acknowledgements, @@ -109,7 +109,7 @@ where }; } - pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { + pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) { debug!("Started InputMessageListener with graceful shutdown support"); while !shutdown.is_shutdown() { @@ -123,7 +123,7 @@ where break; } }, - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("InputMessageListener: Received shutdown"); } } diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs index 5587958f33..ba8a5b9c54 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs @@ -15,9 +15,9 @@ use action_controller::AckActionReceiver; use futures::channel::mpsc; use gateway_client::AcknowledgementReceiver; use log::*; -use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; -use nymsphinx::params::PacketSize; -use nymsphinx::{ +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::params::PacketSize; +use nym_sphinx::{ acknowledgements::AckKey, addressing::clients::Recipient, chunking::fragment::{Fragment, FragmentIdentifier}, @@ -249,7 +249,7 @@ where } } - pub(super) fn start_with_shutdown(self, shutdown: task::ShutdownListener) { + pub(super) fn start_with_shutdown(self, shutdown: nym_task::TaskClient) { let mut acknowledgement_listener = self.acknowledgement_listener; let mut input_message_listener = self.input_message_listener; let mut retransmission_request_listener = self.retransmission_request_listener; diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs index b5c6e6a2a8..194c440720 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs @@ -9,12 +9,12 @@ use crate::client::real_messages_control::acknowledgement_control::PacketDestina use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError}; use crate::client::real_messages_control::real_traffic_stream::RealMessage; use crate::client::replies::reply_controller::ReplyControllerSender; -use client_connections::TransmissionLane; use futures::StreamExt; use log::*; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::chunking::fragment::Fragment; -use nymsphinx::preparer::PreparedFragment; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::chunking::fragment::Fragment; +use nym_sphinx::preparer::PreparedFragment; +use nym_task::connections::TransmissionLane; use rand::{CryptoRng, Rng}; use std::sync::{Arc, Weak}; @@ -137,7 +137,7 @@ where .await } - pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { + pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) { debug!("Started RetransmissionRequestListener with graceful shutdown support"); while !shutdown.is_shutdown() { diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs index c273182d59..4d2445f162 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs @@ -5,7 +5,7 @@ use super::action_controller::{AckActionSender, Action}; use super::SentPacketNotificationReceiver; use futures::StreamExt; use log::*; -use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID}; +use nym_sphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID}; /// Module responsible for starting up retransmission timers. /// It is required because when we send our packet to the `real traffic stream` controlled @@ -37,7 +37,7 @@ impl SentNotificationListener { .unwrap(); } - pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { + 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() { @@ -51,7 +51,7 @@ impl SentNotificationListener { break; } }, - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("SentNotificationListener: Received shutdown"); } } diff --git a/clients/client-core/src/client/real_messages_control/message_handler.rs b/clients/client-core/src/client/real_messages_control/message_handler.rs index 1c80ef519f..cad6974b25 100644 --- a/clients/client-core/src/client/real_messages_control/message_handler.rs +++ b/clients/client-core/src/client/real_messages_control/message_handler.rs @@ -8,22 +8,23 @@ use crate::client::real_messages_control::real_traffic_stream::{ use crate::client::real_messages_control::{AckActionSender, Action}; use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags}; use crate::client::topology_control::{TopologyAccessor, TopologyReadPermit}; -use client_connections::TransmissionLane; use log::{debug, error, info, trace, warn}; -use nymsphinx::acknowledgements::AckKey; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessage, ReplyMessage}; -use nymsphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey}; -use nymsphinx::chunking::fragment::{Fragment, FragmentIdentifier}; -use nymsphinx::message::NymMessage; -use nymsphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS}; -use nymsphinx::preparer::{MessagePreparer, PreparedFragment}; -use nymsphinx::Delay; +use nym_sphinx::acknowledgements::AckKey; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessage, ReplyMessage}; +use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey}; +use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier}; +use nym_sphinx::message::NymMessage; +use nym_sphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS}; +use nym_sphinx::preparer::{MessagePreparer, PreparedFragment}; +use nym_sphinx::Delay; +use nym_task::connections::TransmissionLane; +use nym_topology::{NymTopology, NymTopologyError}; use rand::{CryptoRng, Rng}; +use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; use thiserror::Error; -use topology::{NymTopology, NymTopologyError}; // TODO: move that error elsewhere since it seems to be contaminating different files #[derive(Debug, Clone, Error)] @@ -278,7 +279,7 @@ where reply_surb: ReplySurb, amount: u32, ) -> Result<(), SurbWrappedPreparationError> { - debug!("requesting {amount} reply SURBs from {from:?}"); + debug!("requesting {amount} reply SURBs from {from}"); let surbs_request = ReplyMessage::new_surb_request_message(self.config.sender_address, amount); @@ -294,19 +295,11 @@ where ))) } - // the only difference between this method and `try_send_reply_chunks` is that - // here we are not creating acks as acks are already in memory waiting to get cleared. - // we are only updating their existing delays - pub(crate) async fn try_send_retransmission_reply_chunks( + pub(crate) async fn send_retransmission_reply_chunks( &mut self, - fragments: Vec, - reply_surbs: Vec, + prepared_fragments: Vec, lane: TransmissionLane, - ) -> Result<(), SurbWrappedPreparationError> { - let prepared_fragments = self - .prepare_reply_chunks_for_sending(fragments.clone(), reply_surbs) - .await?; - + ) { let mut real_messages = Vec::with_capacity(prepared_fragments.len()); for prepared in prepared_fragments { @@ -315,33 +308,58 @@ where } self.forward_messages(real_messages, lane).await; - Ok(()) } - pub(crate) async fn try_send_reply_chunks( + pub(crate) async fn try_send_reply_chunks_on_lane( &mut self, target: AnonymousSenderTag, fragments: Vec, reply_surbs: Vec, lane: TransmissionLane, + ) -> Result<(), SurbWrappedPreparationError> { + // TODO: technically this is performing an unnecessary cloning, but in the grand scheme of things + // is it really that bad? + self.try_send_reply_chunks( + target, + fragments.into_iter().map(|f| (lane, f)).collect(), + reply_surbs, + ) + .await + } + + pub(crate) async fn try_send_reply_chunks( + &mut self, + target: AnonymousSenderTag, + fragments: Vec<(TransmissionLane, Fragment)>, + reply_surbs: Vec, ) -> Result<(), SurbWrappedPreparationError> { let prepared_fragments = self - .prepare_reply_chunks_for_sending(fragments.clone(), reply_surbs) + .prepare_reply_chunks_for_sending( + fragments.iter().map(|(_, f)| f.clone()).collect(), + reply_surbs, + ) .await?; let mut pending_acks = Vec::with_capacity(fragments.len()); - let mut real_messages = Vec::with_capacity(fragments.len()); + let mut to_forward: HashMap<_, Vec<_>> = HashMap::new(); for (raw, prepared) in fragments.into_iter().zip(prepared_fragments.into_iter()) { + let lane = raw.0; + let fragment = raw.1; + let real_message = RealMessage::new(prepared.mix_packet, prepared.fragment_identifier); let delay = prepared.total_delay; - let pending_ack = PendingAcknowledgement::new_anonymous(raw, delay, target, false); + let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false); - real_messages.push(real_message); + let entry = to_forward.entry(lane).or_default(); + entry.push(real_message); pending_acks.push(pending_ack); } - self.forward_messages(real_messages, lane).await; + for (lane, real_messages) in to_forward { + self.forward_messages(real_messages, lane).await; + } + self.insert_pending_acks(pending_acks); Ok(()) } @@ -467,7 +485,7 @@ where Ok(prepared_fragment) } - async fn prepare_reply_chunks_for_sending( + pub(crate) async fn prepare_reply_chunks_for_sending( &mut self, fragments: Vec, reply_surbs: Vec, diff --git a/clients/client-core/src/client/real_messages_control/mod.rs b/clients/client-core/src/client/real_messages_control/mod.rs index 8789cbdf6a..69ca797e67 100644 --- a/clients/client-core/src/client/real_messages_control/mod.rs +++ b/clients/client-core/src/client/real_messages_control/mod.rs @@ -21,13 +21,13 @@ use crate::{ }, spawn_future, }; -use client_connections::{ConnectionCommandReceiver, LaneQueueLengths}; use futures::channel::mpsc; use gateway_client::AcknowledgementReceiver; use log::*; -use nymsphinx::acknowledgements::AckKey; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::params::PacketSize; +use nym_sphinx::acknowledgements::AckKey; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::params::PacketSize; +use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths}; use rand::{rngs::OsRng, CryptoRng, Rng}; use std::sync::Arc; use std::time::Duration; @@ -81,7 +81,11 @@ pub struct Config { /// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking /// for more even though in theory they wouldn't need to. - maximum_reply_surb_waiting_period: Duration, + maximum_reply_surb_rerequest_waiting_period: Duration, + + /// Defines maximum amount of time the client is going to wait for reply surbs before + /// deciding it's never going to get them and would drop all pending messages + maximum_reply_surb_drop_waiting_period: Duration, /// Defines maximum amount of time given reply surb is going to be valid for. /// This is going to be superseded by key rotation once implemented. @@ -119,7 +123,8 @@ impl<'a> From<&'a Config> for reply_controller::Config { cfg.minimum_reply_surb_request_size, cfg.maximum_reply_surb_request_size, cfg.maximum_allowed_reply_surb_request_size, - cfg.maximum_reply_surb_waiting_period, + cfg.maximum_reply_surb_rerequest_waiting_period, + cfg.maximum_reply_surb_drop_waiting_period, cfg.maximum_reply_surb_age, cfg.maximum_reply_key_age, ) @@ -161,8 +166,10 @@ impl Config { .maximum_reply_surb_request_size, maximum_allowed_reply_surb_request_size: base_client_debug_config .maximum_allowed_reply_surb_request_size, - maximum_reply_surb_waiting_period: base_client_debug_config - .maximum_reply_surb_waiting_period, + maximum_reply_surb_rerequest_waiting_period: base_client_debug_config + .maximum_reply_surb_rerequest_waiting_period, + maximum_reply_surb_drop_waiting_period: base_client_debug_config + .maximum_reply_surb_drop_waiting_period, maximum_reply_surb_age: base_client_debug_config.maximum_reply_surb_age, maximum_reply_key_age: base_client_debug_config.maximum_reply_key_age, } @@ -263,7 +270,7 @@ impl RealMessagesController { } } - pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) { + pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient) { let mut out_queue_control = self.out_queue_control; let ack_control = self.ack_control; let mut reply_control = self.reply_control; diff --git a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs index 9b75669960..8c8f18503a 100644 --- a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -1,23 +1,25 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use self::sending_delay_controller::SendingDelayController; use crate::client::mix_traffic::BatchMixMessageSender; use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender; use crate::client::topology_control::TopologyAccessor; -use client_connections::{ - ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane, -}; +use crate::client::transmission_buffer::TransmissionBuffer; use futures::task::{Context, Poll}; use futures::{Future, Stream, StreamExt}; use log::*; -use nymsphinx::acknowledgements::AckKey; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::chunking::fragment::FragmentIdentifier; -use nymsphinx::cover::generate_loop_cover_packet; -use nymsphinx::forwarding::packet::MixPacket; -use nymsphinx::params::PacketSize; -use nymsphinx::preparer::PreparedFragment; -use nymsphinx::utils::sample_poisson_duration; +use nym_sphinx::acknowledgements::AckKey; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::chunking::fragment::FragmentIdentifier; +use nym_sphinx::cover::generate_loop_cover_packet; +use nym_sphinx::forwarding::packet::MixPacket; +use nym_sphinx::params::PacketSize; +use nym_sphinx::preparer::PreparedFragment; +use nym_sphinx::utils::sample_poisson_duration; +use nym_task::connections::{ + ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane, +}; use rand::{CryptoRng, Rng}; use std::pin::Pin; use std::sync::Arc; @@ -29,22 +31,7 @@ use tokio::time; #[cfg(target_arch = "wasm32")] use wasm_timer; -use self::{ - sending_delay_controller::SendingDelayController, transmission_buffer::TransmissionBuffer, -}; - mod sending_delay_controller; -mod transmission_buffer; - -#[cfg(not(target_arch = "wasm32"))] -fn get_time_now() -> time::Instant { - time::Instant::now() -} - -#[cfg(target_arch = "wasm32")] -fn get_time_now() -> wasm_timer::Instant { - wasm_timer::Instant::now() -} /// Configurable parameters of the `OutQueueControl` pub(crate) struct Config { @@ -135,7 +122,7 @@ where /// Buffer containing all incoming real messages keyed by transmission lane, that we will send /// out to the mixnet. - transmission_buffer: TransmissionBuffer, + transmission_buffer: TransmissionBuffer, /// Incoming channel for being notified of closed connections, so that we can close lanes /// corresponding to connections. To avoid sending traffic unnecessary @@ -162,6 +149,10 @@ impl From for RealMessage { } impl RealMessage { + pub(crate) fn packet_size(&self) -> usize { + self.mix_packet.sphinx_packet().len() + } + pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self { RealMessage { mix_packet, @@ -207,7 +198,7 @@ where real_receiver, rng, topology_access, - transmission_buffer: Default::default(), + transmission_buffer: TransmissionBuffer::new(), client_connection_rx, lane_queue_lengths, } @@ -264,7 +255,7 @@ where }; if let Err(err) = self.mix_tx.send(vec![next_message]).await { - log::error!("Failed to send: {}", err); + log::error!("Failed to send: {err}"); } // notify ack controller about sending our message only after we actually managed to push it @@ -328,7 +319,9 @@ where fn pop_next_message(&mut self) -> Option { // Pop the next message from the transmission buffer - let (lane, real_next) = self.transmission_buffer.pop_next_message_at_random()?; + let (lane, real_next) = self + .transmission_buffer + .pop_next_message_at_random(&mut self.rng)?; // Update the published queue length let lane_length = self.transmission_buffer.lane_length(&lane); @@ -340,7 +333,7 @@ where fn poll_poisson(&mut self, cx: &mut Context<'_>) -> Poll> { // The average delay could change depending on if backpressure in the downstream channel // (mix_tx) was detected. - self.adjust_current_average_message_sending_delay(); + //self.adjust_current_average_message_sending_delay(); let avg_delay = self.current_average_message_sending_delay(); // Start by checking if we have any incoming messages about closed connections @@ -349,7 +342,6 @@ where if let Poll::Ready(Some(id)) = Pin::new(&mut self.client_connection_rx).poll_next(cx) { match id { ConnectionCommand::Close(id) => self.on_close_connection(id), - ConnectionCommand::ActiveConnections(_) => panic!(), } } @@ -428,7 +420,6 @@ where if let Poll::Ready(Some(id)) = Pin::new(&mut self.client_connection_rx).poll_next(cx) { match id { ConnectionCommand::Close(id) => self.on_close_connection(id), - ConnectionCommand::ActiveConnections(_) => panic!(), } } @@ -469,7 +460,7 @@ where } #[cfg(not(target_arch = "wasm32"))] - fn log_status(&self, shutdown: &mut task::ShutdownListener) { + fn log_status(&self, shutdown: &mut nym_task::TaskClient) { use crate::error::ClientCoreStatusMessage; let packets = self.transmission_buffer.total_size(); @@ -478,14 +469,10 @@ where let mult = self.sending_delay_controller.current_multiplier(); let delay = self.current_average_message_sending_delay().as_millis(); let status_str = if self.config.disable_poisson_packet_distribution { - format!( - "Status: {lanes} lanes, backlog: {:.2} kiB ({packets}), no delay", - backlog - ) + format!("Status: {lanes} lanes, backlog: {backlog:.2} kiB ({packets}), no delay") } else { format!( - "Status: {lanes} lanes, backlog: {:.2} kiB ({packets}), avg delay: {}ms ({mult})", - backlog, delay + "Status: {lanes} lanes, backlog: {backlog:.2} kiB ({packets}), avg delay: {delay}ms ({mult})" ) }; if packets > 1000 { @@ -514,7 +501,7 @@ where } } - pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { + pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) { debug!("Started OutQueueControl with graceful shutdown support"); #[cfg(not(target_arch = "wasm32"))] @@ -525,7 +512,7 @@ where while !shutdown.is_shutdown() { tokio::select! { biased; - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("OutQueueControl: Received shutdown"); } _ = status_timer.tick() => { @@ -542,9 +529,7 @@ where } } } - tokio::time::timeout(Duration::from_secs(5), shutdown.recv()) - .await - .expect("Task stopped without shutdown called"); + shutdown.recv_timeout().await; } #[cfg(target_arch = "wasm32")] diff --git a/clients/client-core/src/client/real_messages_control/real_traffic_stream/sending_delay_controller.rs b/clients/client-core/src/client/real_messages_control/real_traffic_stream/sending_delay_controller.rs index 4ae2632b85..b2d656f763 100644 --- a/clients/client-core/src/client/real_messages_control/real_traffic_stream/sending_delay_controller.rs +++ b/clients/client-core/src/client/real_messages_control/real_traffic_stream/sending_delay_controller.rs @@ -1,14 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use super::get_time_now; +use crate::client::helpers::{get_time_now, Instant}; use std::time::Duration; -#[cfg(not(target_arch = "wasm32"))] -use tokio::time; -#[cfg(target_arch = "wasm32")] -use wasm_timer; - // The minimum time between increasing the average delay between packets. If we hit the ceiling in // the available buffer space we want to take somewhat swift action, but we still need to give a // short time to give the channel a chance reduce pressure. @@ -39,19 +34,11 @@ pub(crate) struct SendingDelayController { lower_bound: u32, /// To make sure we don't change the multiplier to fast, we limit a change to some duration - #[cfg(not(target_arch = "wasm32"))] - time_when_changed: time::Instant, - - #[cfg(target_arch = "wasm32")] - time_when_changed: wasm_timer::Instant, + time_when_changed: Instant, /// If we have a long enough time without any backpressure detected we try reducing the sending /// delay multiplier - #[cfg(not(target_arch = "wasm32"))] - time_when_backpressure_detected: time::Instant, - - #[cfg(target_arch = "wasm32")] - time_when_backpressure_detected: wasm_timer::Instant, + time_when_backpressure_detected: Instant, } impl Default for SendingDelayController { @@ -77,10 +64,12 @@ impl SendingDelayController { self.current_multiplier } + #[cfg(not(target_arch = "wasm32"))] pub(crate) fn min_multiplier(&self) -> u32 { self.lower_bound } + #[cfg(not(target_arch = "wasm32"))] pub(crate) fn max_multiplier(&self) -> u32 { self.upper_bound } diff --git a/clients/client-core/src/client/received_buffer.rs b/clients/client-core/src/client/received_buffer.rs index 2e7876ae62..7d7ffdb021 100644 --- a/clients/client-core/src/client/received_buffer.rs +++ b/clients/client-core/src/client/received_buffer.rs @@ -4,20 +4,20 @@ use crate::client::replies::reply_controller::ReplyControllerSender; use crate::client::replies::reply_storage::SentReplyKeys; use crate::spawn_future; -use crypto::asymmetric::encryption; -use crypto::Digest; use futures::channel::mpsc; use futures::lock::Mutex; use futures::StreamExt; use gateway_client::MixnetMessageReceiver; use log::*; -use nymsphinx::anonymous_replies::requests::{ +use nym_crypto::asymmetric::encryption; +use nym_crypto::Digest; +use nym_sphinx::anonymous_replies::requests::{ RepliableMessage, RepliableMessageContent, ReplyMessage, ReplyMessageContent, }; -use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey}; -use nymsphinx::message::{NymMessage, PlainMessage}; -use nymsphinx::params::ReplySurbKeyDigestAlgorithm; -use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage}; +use nym_sphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey}; +use nym_sphinx::message::{NymMessage, PlainMessage}; +use nym_sphinx::params::ReplySurbKeyDigestAlgorithm; +use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage}; use std::collections::HashSet; use std::sync::Arc; @@ -47,7 +47,7 @@ struct ReceivedMessagesBufferInner { impl ReceivedMessagesBufferInner { fn recover_from_fragment(&mut self, fragment_data: &[u8]) -> Option { - if nymsphinx::cover::is_cover(fragment_data) { + if nym_sphinx::cover::is_cover(fragment_data) { trace!("The message was a loop cover message! Skipping it"); return None; } @@ -399,21 +399,20 @@ impl RequestReceiver { } } - async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { + async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) { debug!("Started RequestReceiver with graceful shutdown support"); while !shutdown.is_shutdown() { tokio::select! { biased; - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("RequestReceiver: Received shutdown"); } request = self.query_receiver.next() => { - match request { - Some(message) => self.handle_message(message).await, - None => { - log::trace!("RequestReceiver: Stopping since channel closed"); - break; - }, + if let Some(message) = request { + self.handle_message(message).await + } else { + log::trace!("RequestReceiver: Stopping since channel closed"); + break; } }, } @@ -439,20 +438,19 @@ impl FragmentedMessageReceiver { } } - async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { + async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) { debug!("Started FragmentedMessageReceiver with graceful shutdown support"); while !shutdown.is_shutdown() { tokio::select! { - new_messages = self.mixnet_packet_receiver.next() => match new_messages { - Some(new_messages) => { + new_messages = self.mixnet_packet_receiver.next() => { + if let Some(new_messages) = new_messages { self.received_buffer.handle_new_received(new_messages).await; - } - None => { + } else { log::trace!("FragmentedMessageReceiver: Stopping since channel closed"); break; } }, - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("FragmentedMessageReceiver: Received shutdown"); } } @@ -490,7 +488,7 @@ impl ReceivedMessagesBufferController { } } - pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) { + pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient) { let mut fragmented_message_receiver = self.fragmented_message_receiver; let mut request_receiver = self.request_receiver; diff --git a/clients/client-core/src/client/replies/reply_controller.rs b/clients/client-core/src/client/replies/reply_controller/mod.rs similarity index 75% rename from clients/client-core/src/client/replies/reply_controller.rs rename to clients/client-core/src/client/replies/reply_controller/mod.rs index 99687527f0..267e2ada1f 100644 --- a/clients/client-core/src/client/replies/reply_controller.rs +++ b/clients/client-core/src/client/replies/reply_controller/mod.rs @@ -4,133 +4,34 @@ use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement; use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError}; use crate::client::replies::reply_storage::CombinedReplyStorage; -use client_connections::TransmissionLane; -use futures::channel::mpsc; +use futures::channel::oneshot; use futures::StreamExt; use log::{debug, error, info, trace, warn}; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; -use nymsphinx::anonymous_replies::ReplySurb; -use nymsphinx::chunking::fragment::{Fragment, FragmentIdentifier}; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::anonymous_replies::ReplySurb; +use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier}; +use nym_task::connections::{ConnectionId, TransmissionLane}; use rand::{CryptoRng, Rng}; use std::cmp::{max, min}; use std::collections::btree_map::Entry; -use std::collections::{BTreeMap, HashMap, VecDeque}; +use std::collections::{BTreeMap, HashMap}; use std::sync::{Arc, Weak}; use std::time::Duration; use time::OffsetDateTime; -#[cfg(not(target_arch = "wasm32"))] -type IntervalStream = tokio_stream::wrappers::IntervalStream; +use crate::client::helpers::new_interval_stream; +use crate::client::transmission_buffer::TransmissionBuffer; +pub(crate) use requests::{ReplyControllerMessage, ReplyControllerReceiver, ReplyControllerSender}; -#[cfg(target_arch = "wasm32")] -type IntervalStream = gloo_timers::future::IntervalStream; - -pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) { - let (tx, rx) = mpsc::unbounded(); - (tx.into(), rx) -} - -#[derive(Debug, Clone)] -pub(crate) struct ReplyControllerSender(mpsc::UnboundedSender); - -impl From> for ReplyControllerSender { - fn from(inner: mpsc::UnboundedSender) -> Self { - ReplyControllerSender(inner) - } -} - -impl ReplyControllerSender { - pub(crate) fn send_retransmission_data( - &self, - recipient: AnonymousSenderTag, - timed_out_ack: Weak, - extra_surb_request: bool, - ) { - self.0 - .unbounded_send(ReplyControllerMessage::RetransmitReply { - recipient, - timed_out_ack, - extra_surb_request, - }) - .expect("ReplyControllerReceiver has died!") - } - - pub(crate) fn send_reply( - &self, - recipient: AnonymousSenderTag, - message: Vec, - lane: TransmissionLane, - ) { - self.0 - .unbounded_send(ReplyControllerMessage::SendReply { - recipient, - message, - lane, - }) - .expect("ReplyControllerReceiver has died!") - } - - pub(crate) fn send_additional_surbs( - &self, - sender_tag: AnonymousSenderTag, - reply_surbs: Vec, - from_surb_request: bool, - ) { - self.0 - .unbounded_send(ReplyControllerMessage::AdditionalSurbs { - sender_tag, - reply_surbs, - from_surb_request, - }) - .expect("ReplyControllerReceiver has died!") - } - - pub(crate) fn send_additional_surbs_request(&self, recipient: Recipient, amount: u32) { - self.0 - .unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest { - recipient: Box::new(recipient), - amount, - }) - .expect("ReplyControllerReceiver has died!") - } -} - -pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver; - -#[derive(Debug)] -pub(crate) enum ReplyControllerMessage { - RetransmitReply { - recipient: AnonymousSenderTag, - timed_out_ack: Weak, - extra_surb_request: bool, - }, - - SendReply { - recipient: AnonymousSenderTag, - message: Vec, - lane: TransmissionLane, - }, - - AdditionalSurbs { - sender_tag: AnonymousSenderTag, - reply_surbs: Vec, - from_surb_request: bool, - }, - - // Should this also be handled in here? it's technically a completely different side of the pipe - // let's see how it works when combined, might split it before creating PR - AdditionalSurbsRequest { - recipient: Box, - amount: u32, - }, -} +pub mod requests; pub struct Config { min_surb_request_size: u32, max_surb_request_size: u32, maximum_allowed_reply_surb_request_size: u32, - max_surb_waiting_period: Duration, + max_surb_rerequest_waiting_period: Duration, + max_surb_drop_waiting_period: Duration, max_reply_surb_age: Duration, max_reply_key_age: Duration, } @@ -140,7 +41,8 @@ impl Config { min_surb_request_size: u32, max_surb_request_size: u32, maximum_allowed_reply_surb_request_size: u32, - max_surb_waiting_period: Duration, + max_surb_rerequest_waiting_period: Duration, + max_surb_drop_waiting_period: Duration, max_reply_surb_age: Duration, max_reply_key_age: Duration, ) -> Self { @@ -148,7 +50,8 @@ impl Config { min_surb_request_size, max_surb_request_size, maximum_allowed_reply_surb_request_size, - max_surb_waiting_period, + max_surb_rerequest_waiting_period, + max_surb_drop_waiting_period, max_reply_surb_age, max_reply_key_age, } @@ -172,7 +75,7 @@ pub struct ReplyController { // of surbs required to send the message through // expected_reliability: f32, request_receiver: ReplyControllerReceiver, - pending_replies: HashMap>, + pending_replies: HashMap>, /// Retransmission packets that have already timed out and are waiting for additional reply SURBs /// so that they could be sent back to the network. Once we receive more SURBs, we should send them ASAP. @@ -204,17 +107,28 @@ where } } - /// Inserts the pending replies into the BACK of the queue fn insert_pending_replies>>( - fn insert_pending_replies>>( + fn insert_pending_replies>( &mut self, recipient: &AnonymousSenderTag, - fragments: V, + fragments: I, + lane: TransmissionLane, ) { - if let Some(existing) = self.pending_replies.get_mut(recipient) { - existing.append(&mut fragments.into()) - } else { - self.pending_replies.insert(*recipient, fragments.into()); - } + self.pending_replies + .entry(*recipient) + .or_insert_with(TransmissionBuffer::new) + .store(&lane, fragments) + } + + fn re_insert_pending_replies( + &mut self, + recipient: &AnonymousSenderTag, + fragments: Vec<(TransmissionLane, Fragment)>, + ) { + // the buffer should ALWAYS exist at this point, if it doesn't, it's a bug... + self.pending_replies + .entry(*recipient) + .or_insert_with(TransmissionBuffer::new) + .store_multiple(fragments) } fn re_insert_pending_retransmission( @@ -244,7 +158,7 @@ where let pending_queue_size = self .pending_replies .get(target) - .map(|pending_queue| pending_queue.len()) + .map(|pending_queue| pending_queue.total_size()) .unwrap_or_default(); let retransmission_queue = self @@ -299,43 +213,62 @@ where } trace!("handling reply to {:?}", recipient_tag); - let fragments = self.message_handler.split_reply_message(data); + let mut fragments = self.message_handler.split_reply_message(data); + let total_size = fragments.len(); + trace!("This reply requires {:?} SURBs", total_size); - let required_surbs = fragments.len(); - trace!("This reply requires {:?} SURBs", required_surbs); - - // TODO: edge case: - // we're making a lot of requests and have to request a lot of surbs - // (but at some point we run out of surbs for surb requests) - - let (surbs, _surbs_left) = self + let available_surbs = self .full_reply_storage .surbs_storage_ref() - .get_reply_surbs(&recipient_tag, required_surbs); + .available_surbs(&recipient_tag); + let min_surbs_threshold = self + .full_reply_storage + .surbs_storage_ref() + .min_surb_threshold(); - if let Some(reply_surbs) = surbs { - if let Err(err) = self - .message_handler - .try_send_reply_chunks(recipient_tag, fragments, reply_surbs, lane) - .await - { - let err = err.return_unused_surbs( - self.full_reply_storage.surbs_storage_ref(), - &recipient_tag, - ); - warn!("failed to send reply to {:?} - {err}", recipient_tag); - - // TODO: should we buffer that data to try again? - } + let max_to_send = if available_surbs > min_surbs_threshold { + min(fragments.len(), available_surbs - min_surbs_threshold) } else { - // we don't have enough surbs for this reply - self.insert_pending_replies(&recipient_tag, fragments); + 0 + }; - if self.should_request_more_surbs(&recipient_tag) { - self.request_reply_surbs_for_queue_clearing(recipient_tag) - .await; + if max_to_send > 0 { + let (surbs, _surbs_left) = self + .full_reply_storage + .surbs_storage_ref() + .get_reply_surbs(&recipient_tag, max_to_send); + + if let Some(reply_surbs) = surbs { + let to_send = fragments.drain(..max_to_send).collect::>(); + if let Err(err) = self + .message_handler + .try_send_reply_chunks_on_lane( + recipient_tag, + to_send.clone(), + reply_surbs, + lane, + ) + .await + { + let err = err.return_unused_surbs( + self.full_reply_storage.surbs_storage_ref(), + &recipient_tag, + ); + warn!("failed to send reply to {recipient_tag}: {err}"); + self.insert_pending_replies(&recipient_tag, to_send, lane); + } } } + + // if there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it + if !fragments.is_empty() { + self.insert_pending_replies(&recipient_tag, fragments, lane); + } + + if self.should_request_more_surbs(&recipient_tag) { + self.request_reply_surbs_for_queue_clearing(recipient_tag) + .await; + } } async fn request_additional_reply_surbs( @@ -398,35 +331,18 @@ where }; let mut to_take = Vec::new(); - let mut to_remove = Vec::new(); - // TODO: once rust 1.66.0 is stabilised on 15.12.22, just change it to - // `.pop_front()` to directly take ownership - for (k, data) in pending.iter() { - let upgraded = match data.upgrade() { - Some(upgraded) => upgraded, - None => { - // we got the ack while the data was waiting in the queue - to_remove.push(*k); - continue; + while to_take.len() < max_to_clear { + if let Some((_, data)) = pending.pop_first() { + // no need to do anything if we failed to upgrade the reference, + // it means we got the ack while the data was waiting in the queue + if let Some(upgraded) = data.upgrade() { + to_take.push(upgraded) } - }; - - to_take.push(upgraded); - - // we have taken as many entries as we could have - if to_take.len() >= max_to_clear { + } else { + // our map is empty! break; } - // TODO: use if upgraded.is_extra_surb_request() to bypass the limit - } - - for ack in &to_take { - pending.remove(&ack.inner_fragment_identifier()); - } - - for id in to_remove { - pending.remove(&id); } if to_take.is_empty() { @@ -447,46 +363,47 @@ where let to_send_vec = to_take.iter().map(|ack| ack.fragment_data()).collect(); - if let Err(err) = self + let prepared_fragments = match self .message_handler - .try_send_retransmission_reply_chunks( - to_send_vec, - surbs_for_reply, - TransmissionLane::Retransmission, - ) + .prepare_reply_chunks_for_sending(to_send_vec, surbs_for_reply) .await { - let err = err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target); - self.re_insert_pending_retransmission(&target, to_take); + Ok(prepared) => prepared, + Err(err) => { + let err = + err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target); + self.re_insert_pending_retransmission(&target, to_take); - warn!( - "failed to clear pending retransmission queue for {:?} - {err}", - target - ); - } + warn!( + "failed to clear pending retransmission queue for {:?} - {err}", + target + ); + return; + } + }; + + // we can't fail at this point, so drop all references to acks so that timer updates wouldn't blow up + drop(to_take); + + self.message_handler + .send_retransmission_reply_chunks(prepared_fragments, TransmissionLane::Retransmission) + .await; } fn pop_at_most_pending_replies( &mut self, from: &AnonymousSenderTag, amount: usize, - ) -> Option> { + ) -> Option> { // if possible, pop all pending replies, if not, pop only entries for which we'd have a reply surb - let total = self.pending_replies.get(from)?.len(); + let total = self.pending_replies.get(from)?.total_size(); trace!("pending queue has {total} elements"); if total == 0 { return None; } - if total < amount { - self.pending_replies.remove(from) - } else { - Some( - self.pending_replies - .get_mut(from)? - .drain(..amount) - .collect(), - ) - } + self.pending_replies + .get_mut(from)? + .pop_at_most_n_next_messages_at_random(amount) } async fn try_clear_pending_queue(&mut self, target: AnonymousSenderTag) { @@ -510,9 +427,9 @@ where // we're guaranteed to not get more entries than we have reply surbs for if let Some(to_send) = self.pop_at_most_pending_replies(&target, max_to_clear) { - let to_send_vec = to_send.iter().cloned().collect::>(); + let to_send_clone = to_send.clone(); - if to_send_vec.is_empty() { + if to_send_clone.is_empty() { panic!( "please let the devs know if you ever see this message (reply_controller.rs)" ); @@ -521,27 +438,22 @@ where let (surbs_for_reply, _) = self .full_reply_storage .surbs_storage_ref() - .get_reply_surbs(&target, to_send_vec.len()); + .get_reply_surbs(&target, to_send_clone.len()); let Some(surbs_for_reply) = surbs_for_reply else { error!("somehow different task has stolen our reply surbs! - this should have been impossible"); - self.insert_pending_replies(&target, to_send); + self.re_insert_pending_replies(&target, to_send); return; }; if let Err(err) = self .message_handler - .try_send_reply_chunks( - target, - to_send_vec, - surbs_for_reply, - TransmissionLane::General, - ) + .try_send_reply_chunks(target, to_send_clone, surbs_for_reply) .await { let err = err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target); - self.insert_pending_replies(&target, to_send); + self.re_insert_pending_replies(&target, to_send); warn!("failed to clear pending queue for {:?} - {err}", target); } } else { @@ -712,6 +624,30 @@ where } } + // to be honest this doesn't make a lot of sense in the context of `connection_id`, + // it should really be asked per tag + fn handle_lane_queue_length( + &self, + connection_id: ConnectionId, + response_channel: oneshot::Sender, + ) { + // TODO: if we ever have duplicate ids for different senders, it means our rng is super weak + // thus I don't think we have to worry about it? + let lane = TransmissionLane::ConnectionId(connection_id); + for buf in self.pending_replies.values() { + if let Some(length) = buf.lane_length(&lane) { + if response_channel.send(length).is_err() { + error!("the requester for lane queue length has dropped the response channel!") + } + return; + } + } + // make sure that if we didn't find that lane, we reply with 0 + if response_channel.send(0).is_err() { + error!("the requester for lane queue length has dropped the response channel!") + } + } + async fn handle_request(&mut self, request: ReplyControllerMessage) { match request { ReplyControllerMessage::RetransmitReply { @@ -735,19 +671,26 @@ where self.handle_received_surbs(sender_tag, reply_surbs, from_surb_request) .await } + ReplyControllerMessage::LaneQueueLength { + connection_id, + response_channel, + } => self.handle_lane_queue_length(connection_id, response_channel), ReplyControllerMessage::AdditionalSurbsRequest { recipient, amount } => { self.handle_surb_request(*recipient, amount).await } } } + // TODO: modify this method to more accurately determine the amount of surbs it needs to request + // it should take into consideration the average latency, sending rate and queue size. + // it should request as many surbs as it takes to saturate its sending rate before next batch arrives async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) { trace!("requesting surbs for queues clearing"); let pending_queue_size = self .pending_replies .get(&target) - .map(|pending_queue| pending_queue.len()) + .map(|pending_queue| pending_queue.total_size()) .unwrap_or_default(); let retransmission_queue = self @@ -787,7 +730,7 @@ where } let Some(last_received) = self.full_reply_storage.surbs_storage_ref().surbs_last_received_at(pending_reply_target) else { - error!("we have {} pending replies for {pending_reply_target}, but we somehow never received any reply surbs from them!", vals.len()); + error!("we have {} pending replies for {pending_reply_target}, but we somehow never received any reply surbs from them!", vals.total_size()); to_remove.push(*pending_reply_target); continue; }; @@ -802,9 +745,13 @@ where let diff = now - last_received_time; - if diff > self.config.max_surb_waiting_period { - warn!("We haven't received any surbs in {:?} from {pending_reply_target}. Going to explicitly ask for more", diff); - to_request.push(*pending_reply_target); + if diff > self.config.max_surb_rerequest_waiting_period { + if diff > self.config.max_surb_drop_waiting_period { + to_remove.push(*pending_reply_target) + } else { + debug!("We haven't received any surbs in {:?} from {pending_reply_target}. Going to explicitly ask for more", diff); + to_request.push(*pending_reply_target); + } } } @@ -883,28 +830,25 @@ where } } - fn create_interval_stream(polling_rate: Duration) -> IntervalStream { - #[cfg(not(target_arch = "wasm32"))] - return tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(polling_rate)); + // #[cfg(not(target_arch = "wasm32"))] + // async fn log_status(&self) { + // todo!() + // } - #[cfg(target_arch = "wasm32")] - return gloo_timers::future::IntervalStream::new(polling_rate.as_millis() as u32); - } - - pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { + pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) { debug!("Started ReplyController with graceful shutdown support"); let polling_rate = Duration::from_secs(5); - let mut stale_inspection = Self::create_interval_stream(polling_rate); + let mut stale_inspection = new_interval_stream(polling_rate); // this is in the order of hours/days so we don't have to poll it that often let polling_rate = Duration::from_secs(self.config.max_reply_surb_age.as_secs() / 10); - let mut invalidation_inspection = Self::create_interval_stream(polling_rate); + let mut invalidation_inspection = new_interval_stream(polling_rate); while !shutdown.is_shutdown() { tokio::select! { biased; - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("ReplyController: Received shutdown"); }, req = self.request_receiver.next() => match req { diff --git a/clients/client-core/src/client/replies/reply_controller/requests.rs b/clients/client-core/src/client/replies/reply_controller/requests.rs new file mode 100644 index 0000000000..a215805088 --- /dev/null +++ b/clients/client-core/src/client/replies/reply_controller/requests.rs @@ -0,0 +1,154 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement; +use futures::channel::{mpsc, oneshot}; +use log::error; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::anonymous_replies::ReplySurb; +use nym_task::connections::{ConnectionId, TransmissionLane}; +use std::sync::Weak; + +pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) { + let (tx, rx) = mpsc::unbounded(); + (tx.into(), rx) +} + +#[derive(Debug, Clone)] +pub struct ReplyControllerSender(mpsc::UnboundedSender); + +impl From> for ReplyControllerSender { + fn from(inner: mpsc::UnboundedSender) -> Self { + ReplyControllerSender(inner) + } +} + +impl ReplyControllerSender { + pub(crate) fn send_retransmission_data( + &self, + recipient: AnonymousSenderTag, + timed_out_ack: Weak, + extra_surb_request: bool, + ) { + self.0 + .unbounded_send(ReplyControllerMessage::RetransmitReply { + recipient, + timed_out_ack, + extra_surb_request, + }) + .expect("ReplyControllerReceiver has died!") + } + + pub(crate) fn send_reply( + &self, + recipient: AnonymousSenderTag, + message: Vec, + lane: TransmissionLane, + ) { + self.0 + .unbounded_send(ReplyControllerMessage::SendReply { + recipient, + message, + lane, + }) + .expect("ReplyControllerReceiver has died!") + } + + pub(crate) fn send_additional_surbs( + &self, + sender_tag: AnonymousSenderTag, + reply_surbs: Vec, + from_surb_request: bool, + ) { + self.0 + .unbounded_send(ReplyControllerMessage::AdditionalSurbs { + sender_tag, + reply_surbs, + from_surb_request, + }) + .expect("ReplyControllerReceiver has died!") + } + + pub(crate) fn send_additional_surbs_request(&self, recipient: Recipient, amount: u32) { + self.0 + .unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest { + recipient: Box::new(recipient), + amount, + }) + .expect("ReplyControllerReceiver has died!") + } + + pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize { + let (response_tx, response_rx) = oneshot::channel(); + self.0 + .unbounded_send(ReplyControllerMessage::LaneQueueLength { + connection_id, + response_channel: response_tx, + }) + .expect("ReplyControllerReceiver has died!"); + + match response_rx.await { + Ok(length) => length, + Err(_) => { + error!("The reply controller has dropped our response channel!"); + // TODO: should we panic here instead? this message implies something weird and unrecoverable has happened + 0 + } + } + } +} + +pub struct ReplyQueueLengths { + reply_controller_sender: ReplyControllerSender, +} + +impl ReplyQueueLengths { + pub fn new(reply_controller_sender: ReplyControllerSender) -> Self { + Self { + reply_controller_sender, + } + } + + pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize { + self.reply_controller_sender + .get_lane_queue_length(connection_id) + .await + } +} + +pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver; + +#[derive(Debug)] +pub(crate) enum ReplyControllerMessage { + RetransmitReply { + recipient: AnonymousSenderTag, + timed_out_ack: Weak, + extra_surb_request: bool, + }, + + SendReply { + recipient: AnonymousSenderTag, + message: Vec, + lane: TransmissionLane, + }, + + AdditionalSurbs { + sender_tag: AnonymousSenderTag, + reply_surbs: Vec, + from_surb_request: bool, + }, + + // this one doesn't belong here either... + LaneQueueLength { + connection_id: ConnectionId, + response_channel: oneshot::Sender, + }, + + // Should this also be handled in here? it's technically a completely different side of the pipe + // let's see how it works when combined, might split it before creating PR + AdditionalSurbsRequest { + recipient: Box, + amount: u32, + }, +} diff --git a/clients/client-core/src/client/replies/reply_storage/backend/browser_backend.rs b/clients/client-core/src/client/replies/reply_storage/backend/browser_backend.rs index 9711ea759f..2753d765cd 100644 --- a/clients/client-core/src/client/replies/reply_storage/backend/browser_backend.rs +++ b/clients/client-core/src/client/replies/reply_storage/backend/browser_backend.rs @@ -5,8 +5,11 @@ use crate::client::replies::reply_storage::backend::Empty; use crate::client::replies::reply_storage::{CombinedReplyStorage, ReplyStorageBackend}; use async_trait::async_trait; +use std::path::PathBuf; + // well, right now we don't have the browser storage : ( // so we keep everything in memory +#[derive(Debug)] pub struct Backend { empty: Empty, } @@ -26,6 +29,18 @@ impl Backend { impl ReplyStorageBackend for Backend { type StorageError = ::StorageError; + async fn new( + debug_config: &crate::config::DebugConfig, + _db_path: Option, + ) -> Result { + Ok(Backend { + empty: Empty { + min_surb_threshold: debug_config.minimum_reply_surb_storage_threshold, + max_surb_threshold: debug_config.maximum_reply_surb_storage_threshold, + }, + }) + } + async fn flush_surb_storage( &mut self, storage: &CombinedReplyStorage, @@ -40,4 +55,8 @@ impl ReplyStorageBackend for Backend { async fn load_surb_storage(&self) -> Result { self.empty.load_surb_storage().await } + + fn get_inactive_storage(&self) -> Result { + self.empty.get_inactive_storage() + } } diff --git a/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/error.rs b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/error.rs index 8164762123..84cac2f7ec 100644 --- a/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/error.rs +++ b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/error.rs @@ -10,6 +10,12 @@ pub enum StorageError { #[error("the provided database path doesn't have a filename defined")] DatabasePathWithoutFilename { provided_path: PathBuf }, + #[error("unable to create the directory for the database")] + DatabasePathUnableToCreateParentDirectory { + provided_path: PathBuf, + source: io::Error, + }, + #[error("failed to rename our databse file - {source}")] DatabaseRenameError { #[source] @@ -50,4 +56,9 @@ pub enum StorageError { details: String, // err: Option> }, + + #[error("failed to create storage")] + FailedToCreateStorage { + source: Box, + }, } diff --git a/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/manager.rs b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/manager.rs index 578c0021a3..40c4da18fa 100644 --- a/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/manager.rs +++ b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/manager.rs @@ -20,6 +20,16 @@ impl StorageManager { database_path: P, fresh: bool, ) -> Result { + // ensure the whole directory structure exists + if let Some(parent_dir) = database_path.as_ref().parent() { + std::fs::create_dir_all(parent_dir).map_err(|source| { + StorageError::DatabasePathUnableToCreateParentDirectory { + provided_path: database_path.as_ref().to_path_buf(), + source, + } + })?; + } + let mut opts = sqlx::sqlite::SqliteConnectOptions::new() .filename(database_path) .create_if_missing(fresh); diff --git a/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/mod.rs b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/mod.rs index 9fd48bcd2c..8830a33211 100644 --- a/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/mod.rs +++ b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/mod.rs @@ -1,6 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::client::base_client::non_wasm_helpers; use crate::client::replies::reply_storage::backend::fs_backend::manager::StorageManager; use crate::client::replies::reply_storage::backend::fs_backend::models::{ ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag, StoredSurbSender, @@ -11,7 +12,7 @@ use crate::client::replies::reply_storage::{ }; use async_trait::async_trait; use log::{error, info, warn}; -use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use std::fs; use std::path::{Path, PathBuf}; use time::OffsetDateTime; @@ -22,11 +23,49 @@ mod error; mod manager; mod models; +#[derive(Debug)] +enum StorageManagerState { + Storage(StorageManager), + Inactive(InactiveMetadata), +} + +// When the storage backaed is initialized as inactive, it will still contain metadata parameters +// that will be needed when the in-mem storage is fetched for use. +#[derive(Debug)] +struct InactiveMetadata { + pub minimum_reply_surb_storage_threshold: usize, + pub maximum_reply_surb_storage_threshold: usize, +} + +impl StorageManagerState { + fn get(&self) -> &StorageManager { + match self { + StorageManagerState::Storage(manager) => manager, + StorageManagerState::Inactive(_) => { + panic!("tried to get storage of an inactive backend") + } + } + } + + fn get_mut(&mut self) -> &mut StorageManager { + match self { + StorageManagerState::Storage(manager) => manager, + StorageManagerState::Inactive(_) => { + panic!("tried to get storage of an inactive backend") + } + } + } + + fn is_active(&self) -> bool { + matches!(self, StorageManagerState::Storage(_)) + } +} + #[derive(Debug)] pub struct Backend { temporary_old_path: Option, database_path: PathBuf, - manager: StorageManager, + manager: StorageManagerState, } impl Backend { @@ -40,17 +79,32 @@ impl Backend { }); } + let manager = StorageManager::init(database_path, true).await?; + manager.create_status_table().await?; + let backend = Backend { temporary_old_path: None, database_path: owned_path, - manager: StorageManager::init(database_path, true).await?, + manager: StorageManagerState::Storage(manager), }; - backend.manager.create_status_table().await?; - Ok(backend) } + pub fn new_inactive( + minimum_reply_surb_storage_threshold: usize, + maximum_reply_surb_storage_threshold: usize, + ) -> Self { + Backend { + temporary_old_path: None, + database_path: PathBuf::new(), + manager: StorageManagerState::Inactive(InactiveMetadata { + minimum_reply_surb_storage_threshold, + maximum_reply_surb_storage_threshold, + }), + } + } + pub async fn try_load>(database_path: P) -> Result { let owned_path: PathBuf = database_path.as_ref().into(); if owned_path.file_name().is_none() { @@ -101,30 +155,33 @@ impl Backend { // (assuming no key rotation has happened) // but the way it's currently coded, everyone will purge old data let since_last_flush = OffsetDateTime::now_utc() - last_flush; - if since_last_flush.whole_days() > 0 { - info!("it's been over {} days and {} hours since we last used our data store. our reply surbs are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours()); + let days = since_last_flush.whole_days(); + let hours = since_last_flush.whole_hours() % 24; + + if days > 0 { + info!("it's been over {days} days and {hours} hours since we last used our data store. our reply surbs are already outdated - we're going to purge them now."); manager.delete_all_reply_surb_data().await?; } - if since_last_flush.whole_days() > 1 { - info!("it's been over {} days and {} hours since we last used our data store. our reply keys are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours()); + if days > 1 { + info!("it's been over {days} days and {hours} hours since we last used our data store. our reply keys are already outdated - we're going to purge them now."); manager.delete_all_reply_keys().await?; } - if since_last_flush.whole_days() > 2 { - info!("it's been over {} days and {} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours()); + if days > 2 { + info!("it's been over {days} days and {hours} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now."); manager.delete_all_tags().await?; } Ok(Backend { temporary_old_path: None, database_path: owned_path, - manager, + manager: StorageManagerState::Storage(manager), }) } async fn close_pool(&mut self) { - self.manager.connection_pool.close().await; + self.manager.get_mut().connection_pool.close().await; } async fn rotate(&mut self) -> Result<(), StorageError> { @@ -143,8 +200,9 @@ impl Backend { fs::rename(&self.database_path, &temp_old) .map_err(|err| StorageError::DatabaseRenameError { source: err })?; - self.manager = StorageManager::init(&self.database_path, true).await?; - self.manager.create_status_table().await?; + self.manager = + StorageManagerState::Storage(StorageManager::init(&self.database_path, true).await?); + self.manager.get_mut().create_status_table().await?; self.temporary_old_path = Some(temp_old); Ok(()) @@ -161,26 +219,27 @@ impl Backend { } async fn start_storage_flush(&self) -> Result<(), StorageError> { - Ok(self.manager.set_flush_status(true).await?) + Ok(self.manager.get().set_flush_status(true).await?) } async fn end_storage_flush(&self) -> Result<(), StorageError> { self.manager + .get() .set_previous_flush_timestamp(OffsetDateTime::now_utc().unix_timestamp()) .await?; - Ok(self.manager.set_flush_status(false).await?) + Ok(self.manager.get().set_flush_status(false).await?) } async fn start_client_use(&self) -> Result<(), StorageError> { - Ok(self.manager.set_client_in_use_status(true).await?) + Ok(self.manager.get().set_client_in_use_status(true).await?) } async fn stop_client_use(&self) -> Result<(), StorageError> { - Ok(self.manager.set_client_in_use_status(false).await?) + Ok(self.manager.get().set_client_in_use_status(false).await?) } async fn get_stored_tags(&self) -> Result { - let stored = self.manager.get_tags().await?; + let stored = self.manager.get().get_tags().await?; // stop at the first instance of corruption. if even a single entry is malformed, // something weird has happened and we can't trust the rest of the data @@ -196,6 +255,7 @@ impl Backend { for map_ref in tags.as_raw_iter() { let (recipient, tag) = map_ref.pair(); self.manager + .get() .insert_tag(StoredSenderTag::new(*recipient, *tag)) .await?; } @@ -203,7 +263,7 @@ impl Backend { } async fn get_stored_reply_keys(&self) -> Result { - let stored = self.manager.get_reply_keys().await?; + let stored = self.manager.get().get_reply_keys().await?; // stop at the first instance of corruption. if even a single entry is malformed, // something weird has happened and we can't trust the rest of the data @@ -219,6 +279,7 @@ impl Backend { for map_ref in reply_keys.as_raw_iter() { let (digest, key) = map_ref.pair(); self.manager + .get() .insert_reply_key(StoredReplyKey::new(*digest, *key)) .await?; } @@ -226,7 +287,7 @@ impl Backend { } async fn get_stored_reply_surbs(&self) -> Result { - let surb_senders = self.manager.get_surb_senders().await?; + let surb_senders = self.manager.get().get_surb_senders().await?; let metadata = self.get_reply_surb_storage_metadata().await?; let mut received_surbs = Vec::with_capacity(surb_senders.len()); @@ -236,6 +297,7 @@ impl Backend { sender.try_into()?; let stored_surbs = self .manager + .get() .get_reply_surbs(sender_id) .await? .into_iter() @@ -263,6 +325,7 @@ impl Backend { let (tag, received_surbs) = map_ref.pair(); let sender_id = self .manager + .get() .insert_surb_sender(StoredSurbSender::new( *tag, received_surbs.surbs_last_received_at(), @@ -271,6 +334,7 @@ impl Backend { for reply_surb in received_surbs.surbs_ref() { self.manager + .get() .insert_reply_surb(StoredReplySurb::new(sender_id, reply_surb)) .await? } @@ -282,6 +346,7 @@ impl Backend { &self, ) -> Result { self.manager + .get() .get_reply_surb_storage_metadata() .await .map_err(Into::into) @@ -292,6 +357,7 @@ impl Backend { reply_surbs: &ReceivedReplySurbsMap, ) -> Result<(), StorageError> { self.manager + .get() .insert_reply_surb_storage_metadata(ReplySurbStorageMetadata::new( reply_surbs.min_surb_threshold(), reply_surbs.max_surb_threshold(), @@ -305,6 +371,24 @@ impl Backend { impl ReplyStorageBackend for Backend { type StorageError = error::StorageError; + async fn new( + debug_config: &crate::config::DebugConfig, + db_path: Option, + ) -> Result { + non_wasm_helpers::setup_fs_reply_surb_backend(db_path, debug_config) + .await + .map_err(|err| { + log::error!("Failed to create storage: {err}"); + Self::StorageError::FailedToCreateStorage { + source: Box::new(err), + } + }) + } + + fn is_active(&self) -> bool { + self.manager.is_active() + } + async fn start_storage_session(&self) -> Result<(), Self::StorageError> { self.start_client_use().await } @@ -342,6 +426,18 @@ impl ReplyStorageBackend for Backend { Ok(CombinedReplyStorage::load(reply_keys, reply_surbs, tags)) } + fn get_inactive_storage(&self) -> Result { + match self.manager { + StorageManagerState::Storage(_) => { + panic!("tried to get inactive storage from an active storage backend") + } + StorageManagerState::Inactive(ref state) => Ok(CombinedReplyStorage::new( + state.minimum_reply_surb_storage_threshold, + state.maximum_reply_surb_storage_threshold, + )), + } + } + async fn stop_storage_session(self) -> Result<(), Self::StorageError> { self.stop_client_use().await } diff --git a/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/models.rs b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/models.rs index 65088ad05e..c7b5196654 100644 --- a/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/models.rs +++ b/clients/client-core/src/client/replies/reply_storage/backend/fs_backend/models.rs @@ -3,13 +3,13 @@ use crate::client::replies::reply_storage::backend::fs_backend::error::StorageError; use crate::client::replies::reply_storage::key_storage::UsedReplyKey; -use crypto::generic_array::typenum::Unsigned; -use crypto::Digest; -use nymsphinx::addressing::clients::{Recipient, RecipientBytes}; -use nymsphinx::anonymous_replies::encryption_key::EncryptionKeyDigest; -use nymsphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE}; -use nymsphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey, SurbEncryptionKeySize}; -use nymsphinx::params::ReplySurbKeyDigestAlgorithm; +use nym_crypto::generic_array::typenum::Unsigned; +use nym_crypto::Digest; +use nym_sphinx::addressing::clients::{Recipient, RecipientBytes}; +use nym_sphinx::anonymous_replies::encryption_key::EncryptionKeyDigest; +use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE}; +use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey, SurbEncryptionKeySize}; +use nym_sphinx::params::ReplySurbKeyDigestAlgorithm; #[derive(Debug, Clone)] pub(crate) struct StoredSenderTag { @@ -44,8 +44,7 @@ impl TryFrom for (RecipientBytes, AnonymousSenderTag) { let Ok(sender_tag_bytes) = value.tag.try_into() else { return Err(StorageError::CorruptedData { details: format!( - "the retrieved sender tag has length of {tag_len} while {} was expected", - SENDER_TAG_SIZE + "the retrieved sender tag has length of {tag_len} while {SENDER_TAG_SIZE} was expected", ), }); }; @@ -132,8 +131,7 @@ impl TryFrom for (AnonymousSenderTag, i64) { let Ok(sender_tag_bytes) = value.tag.try_into() else { return Err(StorageError::CorruptedData { details: format!( - "the retrieved sender tag has length of {tag_len} while {} was expected", - SENDER_TAG_SIZE + "the retrieved sender tag has length of {tag_len} while {SENDER_TAG_SIZE} was expected", ), }); }; diff --git a/clients/client-core/src/client/replies/reply_storage/backend/mod.rs b/clients/client-core/src/client/replies/reply_storage/backend/mod.rs index d80231ae1e..3113b3a95d 100644 --- a/clients/client-core/src/client/replies/reply_storage/backend/mod.rs +++ b/clients/client-core/src/client/replies/reply_storage/backend/mod.rs @@ -3,7 +3,7 @@ use crate::client::replies::reply_storage::CombinedReplyStorage; use async_trait::async_trait; -use std::error::Error; +use std::{error::Error, path::PathBuf}; use thiserror::Error; #[cfg(target_arch = "wasm32")] @@ -19,16 +19,27 @@ pub mod fs_backend; #[error("no information provided")] pub struct UndefinedError; +#[derive(Debug)] pub struct Empty { // we need to keep 'basic' metadata here to "load" the CombinedReplyStorage - min_surb_threshold: usize, - max_surb_threshold: usize, + pub min_surb_threshold: usize, + pub max_surb_threshold: usize, } #[async_trait] impl ReplyStorageBackend for Empty { type StorageError = UndefinedError; + async fn new( + debug_config: &crate::config::DebugConfig, + _db_path: Option, + ) -> Result { + Ok(Self { + min_surb_threshold: debug_config.minimum_reply_surb_storage_threshold, + max_surb_threshold: debug_config.maximum_reply_surb_storage_threshold, + }) + } + async fn flush_surb_storage( &mut self, _storage: &CombinedReplyStorage, @@ -49,12 +60,28 @@ impl ReplyStorageBackend for Empty { self.max_surb_threshold, )) } + + fn get_inactive_storage(&self) -> Result { + Ok(CombinedReplyStorage::new( + self.min_surb_threshold, + self.max_surb_threshold, + )) + } } #[async_trait] pub trait ReplyStorageBackend: Sized { type StorageError: Error + 'static; + async fn new( + debug_config: &crate::config::DebugConfig, + db_path: Option, + ) -> Result; + + fn is_active(&self) -> bool { + true + } + async fn start_storage_session(&self) -> Result<(), Self::StorageError> { Ok(()) } @@ -72,6 +99,11 @@ pub trait ReplyStorageBackend: Sized { async fn load_surb_storage(&self) -> Result; + /// In the case the storage backend is initialized in an inactive state (persisting data is + /// disabled), we might still need to fetch the (in-mem) storage and the parameters it was + /// created with. + fn get_inactive_storage(&self) -> Result; + async fn stop_storage_session(self) -> Result<(), Self::StorageError> { Ok(()) } diff --git a/clients/client-core/src/client/replies/reply_storage/key_storage.rs b/clients/client-core/src/client/replies/reply_storage/key_storage.rs index 9c68f40e06..310a44e0eb 100644 --- a/clients/client-core/src/client/replies/reply_storage/key_storage.rs +++ b/clients/client-core/src/client/replies/reply_storage/key_storage.rs @@ -3,8 +3,8 @@ use dashmap::iter::Iter; use dashmap::DashMap; -use nymsphinx::anonymous_replies::encryption_key::EncryptionKeyDigest; -use nymsphinx::anonymous_replies::SurbEncryptionKey; +use nym_sphinx::anonymous_replies::encryption_key::EncryptionKeyDigest; +use nym_sphinx::anonymous_replies::SurbEncryptionKey; use std::ops::Deref; use std::sync::Arc; use time::OffsetDateTime; diff --git a/clients/client-core/src/client/replies/reply_storage/mod.rs b/clients/client-core/src/client/replies/reply_storage/mod.rs index 83c0e0e34f..5b8c7b9dee 100644 --- a/clients/client-core/src/client/replies/reply_storage/mod.rs +++ b/clients/client-core/src/client/replies/reply_storage/mod.rs @@ -37,7 +37,7 @@ where pub async fn flush_on_shutdown( mut self, mem_state: CombinedReplyStorage, - mut shutdown: task::ShutdownListener, + mut shutdown: nym_task::TaskClient, ) { use log::{debug, error, info, warn}; diff --git a/clients/client-core/src/client/replies/reply_storage/surb_storage.rs b/clients/client-core/src/client/replies/reply_storage/surb_storage.rs index ed2fd8d846..cb88e30a90 100644 --- a/clients/client-core/src/client/replies/reply_storage/surb_storage.rs +++ b/clients/client-core/src/client/replies/reply_storage/surb_storage.rs @@ -4,8 +4,8 @@ use dashmap::iter::Iter; use dashmap::DashMap; use log::trace; -use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; -use nymsphinx::anonymous_replies::ReplySurb; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::anonymous_replies::ReplySurb; use std::collections::VecDeque; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; diff --git a/clients/client-core/src/client/replies/reply_storage/tag_storage.rs b/clients/client-core/src/client/replies/reply_storage/tag_storage.rs index c91d2b003e..892018b6fc 100644 --- a/clients/client-core/src/client/replies/reply_storage/tag_storage.rs +++ b/clients/client-core/src/client/replies/reply_storage/tag_storage.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use dashmap::DashMap; -use nymsphinx::addressing::clients::{Recipient, RecipientBytes}; -use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::addressing::clients::{Recipient, RecipientBytes}; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use std::sync::Arc; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] diff --git a/clients/client-core/src/client/topology_control.rs b/clients/client-core/src/client/topology_control.rs deleted file mode 100644 index ad9e4a4ce6..0000000000 --- a/clients/client-core/src/client/topology_control.rs +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright 2021-2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::spawn_future; -use futures::StreamExt; -use log::*; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::params::DEFAULT_NUM_MIX_HOPS; -use rand::seq::SliceRandom; -use rand::thread_rng; -use std::ops::Deref; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::{RwLock, RwLockReadGuard}; -use topology::{nym_topology_from_detailed, NymTopology, NymTopologyError}; -use url::Url; - -// I'm extremely curious why compiler NEVER complained about lack of Debug here before -#[derive(Debug)] -pub struct TopologyAccessorInner(Option); - -impl AsRef> for TopologyAccessorInner { - fn as_ref(&self) -> &Option { - &self.0 - } -} - -impl TopologyAccessorInner { - fn new() -> Self { - TopologyAccessorInner(None) - } - - fn update(&mut self, new: Option) { - self.0 = new; - } -} - -pub struct TopologyReadPermit<'a> { - permit: RwLockReadGuard<'a, TopologyAccessorInner>, -} - -impl<'a> Deref for TopologyReadPermit<'a> { - type Target = TopologyAccessorInner; - - fn deref(&self) -> &Self::Target { - &self.permit - } -} - -impl<'a> TopologyReadPermit<'a> { - /// Using provided topology read permit, tries to get an immutable reference to the underlying - /// topology. For obvious reasons the lifetime of the topology reference is bound to the permit. - pub(super) fn try_get_valid_topology_ref( - &'a self, - ack_recipient: &Recipient, - packet_recipient: Option<&Recipient>, - ) -> Result<&'a NymTopology, NymTopologyError> { - // 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through? - let topology = self - .permit - .as_ref() - .as_ref() - .ok_or(NymTopologyError::EmptyNetworkTopology)?; - - // 2. does it have any mixnode at all? - // 3. does it have any gateways at all? - // 4. does it have a mixnode on each layer? - topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS)?; - - // 5. does it contain OUR gateway (so that we could create an ack packet)? - if !topology.gateway_exists(ack_recipient.gateway()) { - return Err(NymTopologyError::NonExistentGatewayError { - identity_key: ack_recipient.gateway().to_base58_string(), - }); - } - - // 6. for our target recipient, does it contain THEIR gateway (so that we could create - if let Some(recipient) = packet_recipient { - if !topology.gateway_exists(recipient.gateway()) { - return Err(NymTopologyError::NonExistentGatewayError { - identity_key: recipient.gateway().to_base58_string(), - }); - } - } - - Ok(topology) - } -} - -impl<'a> From> for TopologyReadPermit<'a> { - fn from(read_permit: RwLockReadGuard<'a, TopologyAccessorInner>) -> Self { - TopologyReadPermit { - permit: read_permit, - } - } -} - -#[derive(Clone, Debug)] -pub struct TopologyAccessor { - // `RwLock` *seems to* be the better approach for this as write access is only requested every - // few seconds, while reads are needed every single packet generated. - // However, proper benchmarks will be needed to determine if `RwLock` is indeed a better - // approach than a `Mutex` - inner: Arc>, -} - -impl TopologyAccessor { - pub fn new() -> Self { - TopologyAccessor { - inner: Arc::new(RwLock::new(TopologyAccessorInner::new())), - } - } - - pub async fn get_read_permit(&self) -> TopologyReadPermit<'_> { - self.inner.read().await.into() - } - - async fn update_global_topology(&self, new_topology: Option) { - self.inner.write().await.update(new_topology); - } - - // only used by the client at startup to get a slightly more reasonable error message - // (currently displays as unused because health checker is disabled due to required changes) - pub async fn ensure_is_routable(&self) -> Result<(), NymTopologyError> { - match &self.inner.read().await.0 { - None => Err(NymTopologyError::EmptyNetworkTopology), - Some(ref topology) => topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS), - } - } -} - -impl Default for TopologyAccessor { - fn default() -> Self { - TopologyAccessor::new() - } -} - -pub struct TopologyRefresherConfig { - validator_api_urls: Vec, - refresh_rate: Duration, - client_version: String, -} - -impl TopologyRefresherConfig { - pub fn new( - validator_api_urls: Vec, - refresh_rate: Duration, - client_version: String, - ) -> Self { - TopologyRefresherConfig { - validator_api_urls, - refresh_rate, - client_version, - } - } -} - -pub struct TopologyRefresher { - validator_client: validator_client::client::ApiClient, - client_version: String, - - validator_api_urls: Vec, - topology_accessor: TopologyAccessor, - refresh_rate: Duration, - - currently_used_api: usize, - was_latest_valid: bool, -} - -impl TopologyRefresher { - pub fn new(mut cfg: TopologyRefresherConfig, topology_accessor: TopologyAccessor) -> Self { - cfg.validator_api_urls.shuffle(&mut thread_rng()); - - TopologyRefresher { - validator_client: validator_client::client::ApiClient::new( - cfg.validator_api_urls[0].clone(), - ), - client_version: cfg.client_version, - validator_api_urls: cfg.validator_api_urls, - topology_accessor, - refresh_rate: cfg.refresh_rate, - currently_used_api: 0, - was_latest_valid: true, - } - } - - fn use_next_validator_api(&mut self) { - if self.validator_api_urls.len() == 1 { - warn!("There's only a single validator API available - it won't be possible to use a different one"); - return; - } - - self.currently_used_api = (self.currently_used_api + 1) % self.validator_api_urls.len(); - self.validator_client - .change_validator_api(self.validator_api_urls[self.currently_used_api].clone()) - } - - /// Verifies whether nodes a reasonably distributed among all mix layers. - /// - /// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3. - /// However, this is a rather unrealistic expectation, instead we check whether there exists - /// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure. - /// - /// # Arguments - /// - /// * `topology`: active topology constructed from validator api data - fn check_layer_distribution(&self, active_topology: &NymTopology) -> bool { - let mixes = active_topology.mixes(); - let mixnodes_count = active_topology.num_mixnodes(); - - if active_topology.gateways().is_empty() { - return false; - } - - // trivial check to see if have at least a single node on each layer (regardless of active set size) - if mixes.get(&1).is_none() || mixes.get(&2).is_none() || mixes.get(&3).is_none() { - return false; - } - - let upper_bound = (mixnodes_count as f32 * 0.66) as usize; - let lower_bound = (mixnodes_count as f32 * 0.15) as usize; - - let layer1 = mixes.get(&1).unwrap().len(); - let layer2 = mixes.get(&2).unwrap().len(); - let layer3 = mixes.get(&3).unwrap().len(); - - if layer1 < lower_bound || layer1 > upper_bound { - warn!( - "nodes: {}, layer1: {}, layer2: {}, layer3: {}", - mixnodes_count, layer1, layer2, layer3 - ); - return false; - } - - if layer2 < lower_bound || layer2 > upper_bound { - warn!( - "nodes: {}, layer1: {}, layer2: {}, layer3: {}", - mixnodes_count, layer1, layer2, layer3 - ); - return false; - } - - if layer3 < lower_bound || layer3 > upper_bound { - warn!( - "nodes: {}, layer1: {}, layer2: {}, layer3: {}", - mixnodes_count, layer1, layer2, layer3 - ); - return false; - } - - true - } - - async fn get_current_compatible_topology(&self) -> Option { - // TODO: optimization for the future: - // only refresh mixnodes on timer and refresh gateways only when - // we have to send to a new, unknown, gateway - - let mixnodes = match self.validator_client.get_cached_active_mixnodes().await { - Err(err) => { - error!("failed to get network mixnodes - {err}"); - return None; - } - Ok(mixes) => mixes, - }; - - let gateways = match self.validator_client.get_cached_gateways().await { - Err(err) => { - error!("failed to get network gateways - {err}"); - return None; - } - Ok(gateways) => gateways, - }; - - let topology = nym_topology_from_detailed(mixnodes, gateways) - .filter_system_version(&self.client_version); - - if !self.check_layer_distribution(&topology) { - warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used."); - None - } else { - Some(topology) - } - } - - pub async fn refresh(&mut self) { - trace!("Refreshing the topology"); - let new_topology = self.get_current_compatible_topology().await; - - if new_topology.is_none() { - self.use_next_validator_api(); - } - - if new_topology.is_none() && self.was_latest_valid { - // if we failed to grab this topology, but the one before it was alright, let's assume - // validator had a tiny hiccup and use the old data - warn!("we're going to keep on using the old topology for this iteration"); - self.was_latest_valid = false; - return; - } else if new_topology.is_some() { - self.was_latest_valid = true; - } - - self.topology_accessor - .update_global_topology(new_topology) - .await; - } - - pub async fn ensure_topology_is_routable(&self) -> Result<(), NymTopologyError> { - self.topology_accessor.ensure_is_routable().await - } - - pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) { - spawn_future(async move { - debug!("Started TopologyRefresher with graceful shutdown support"); - - #[cfg(not(target_arch = "wasm32"))] - let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval( - self.refresh_rate, - )); - - #[cfg(target_arch = "wasm32")] - let mut interval = - gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32); - - while !shutdown.is_shutdown() { - tokio::select! { - _ = interval.next() => { - self.refresh().await; - }, - _ = shutdown.recv() => { - log::trace!("TopologyRefresher: Received shutdown"); - }, - } - } - shutdown.recv_timeout().await; - log::debug!("TopologyRefresher: Exiting"); - }) - } -} diff --git a/clients/client-core/src/client/topology_control/accessor.rs b/clients/client-core/src/client/topology_control/accessor.rs new file mode 100644 index 0000000000..fcb272c934 --- /dev/null +++ b/clients/client-core/src/client/topology_control/accessor.rs @@ -0,0 +1,154 @@ +// Copyright 2021-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::params::DEFAULT_NUM_MIX_HOPS; +use nym_topology::{NymTopology, NymTopologyError}; +use std::ops::Deref; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use tokio::sync::{Notify, RwLock, RwLockReadGuard}; + +#[derive(Debug)] +pub struct TopologyAccessorInner { + controlled_manually: AtomicBool, + released_manual_control: Notify, + // `RwLock` *seems to* be the better approach for this as write access is only requested every + // few seconds, while reads are needed every single packet generated. + // However, proper benchmarks will be needed to determine if `RwLock` is indeed a better + // approach than a `Mutex` + topology: RwLock>, +} + +impl TopologyAccessorInner { + fn new() -> Self { + TopologyAccessorInner { + controlled_manually: AtomicBool::new(false), + released_manual_control: Notify::new(), + topology: RwLock::new(None), + } + } + + async fn update(&self, new: Option) { + *self.topology.write().await = new; + } +} + +pub struct TopologyReadPermit<'a> { + permit: RwLockReadGuard<'a, Option>, +} + +impl<'a> Deref for TopologyReadPermit<'a> { + type Target = Option; + + fn deref(&self) -> &Self::Target { + &self.permit + } +} + +impl<'a> TopologyReadPermit<'a> { + /// Using provided topology read permit, tries to get an immutable reference to the underlying + /// topology. For obvious reasons the lifetime of the topology reference is bound to the permit. + pub(crate) fn try_get_valid_topology_ref( + &'a self, + ack_recipient: &Recipient, + packet_recipient: Option<&Recipient>, + ) -> Result<&'a NymTopology, NymTopologyError> { + // 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through? + let topology = self + .permit + .as_ref() + .ok_or(NymTopologyError::EmptyNetworkTopology)?; + + // 2. does it have any mixnode at all? + // 3. does it have any gateways at all? + // 4. does it have a mixnode on each layer? + topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS)?; + + // 5. does it contain OUR gateway (so that we could create an ack packet)? + if !topology.gateway_exists(ack_recipient.gateway()) { + return Err(NymTopologyError::NonExistentGatewayError { + identity_key: ack_recipient.gateway().to_base58_string(), + }); + } + + // 6. for our target recipient, does it contain THEIR gateway (so that we could create + if let Some(recipient) = packet_recipient { + if !topology.gateway_exists(recipient.gateway()) { + return Err(NymTopologyError::NonExistentGatewayError { + identity_key: recipient.gateway().to_base58_string(), + }); + } + } + + Ok(topology) + } +} + +impl<'a> From>> for TopologyReadPermit<'a> { + fn from(read_permit: RwLockReadGuard<'a, Option>) -> Self { + TopologyReadPermit { + permit: read_permit, + } + } +} + +#[derive(Clone, Debug)] +pub struct TopologyAccessor { + inner: Arc, +} + +impl TopologyAccessor { + pub fn new() -> Self { + TopologyAccessor { + inner: Arc::new(TopologyAccessorInner::new()), + } + } + + pub fn controlled_manually(&self) -> bool { + self.inner.controlled_manually.load(Ordering::SeqCst) + } + + pub async fn get_read_permit(&self) -> TopologyReadPermit<'_> { + self.inner.topology.read().await.into() + } + + pub(crate) async fn update_global_topology(&self, new_topology: Option) { + self.inner.update(new_topology).await; + } + + pub(crate) async fn wait_for_released_manual_control(&self) { + self.inner.released_manual_control.notified().await + } + + pub async fn current_topology(&self) -> Option { + self.inner.topology.read().await.clone() + } + + pub async fn manually_change_topology(&self, new_topology: NymTopology) { + self.inner.controlled_manually.store(true, Ordering::SeqCst); + self.inner.update(Some(new_topology)).await; + } + + pub fn release_manual_control(&self) { + self.inner + .controlled_manually + .store(false, Ordering::SeqCst); + self.inner.released_manual_control.notify_waiters(); + } + + // only used by the client at startup to get a slightly more reasonable error message + // (currently displays as unused because health checker is disabled due to required changes) + pub async fn ensure_is_routable(&self) -> Result<(), NymTopologyError> { + match self.inner.topology.read().await.deref() { + None => Err(NymTopologyError::EmptyNetworkTopology), + Some(ref topology) => topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS), + } + } +} + +impl Default for TopologyAccessor { + fn default() -> Self { + TopologyAccessor::new() + } +} diff --git a/clients/client-core/src/client/topology_control/mod.rs b/clients/client-core/src/client/topology_control/mod.rs new file mode 100644 index 0000000000..168767cd3e --- /dev/null +++ b/clients/client-core/src/client/topology_control/mod.rs @@ -0,0 +1,115 @@ +// Copyright 2021-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::spawn_future; +pub(crate) use accessor::{TopologyAccessor, TopologyReadPermit}; +use futures::StreamExt; +use log::*; +use nym_topology::provider_trait::TopologyProvider; +use nym_topology::NymTopologyError; +use std::time::Duration; + +mod accessor; +pub(crate) mod nym_api_provider; + +// TODO: move it to config later +const MAX_FAILURE_COUNT: usize = 10; + +pub struct TopologyRefresherConfig { + refresh_rate: Duration, +} + +impl TopologyRefresherConfig { + pub fn new(refresh_rate: Duration) -> Self { + TopologyRefresherConfig { refresh_rate } + } +} + +pub struct TopologyRefresher { + topology_provider: Box, + topology_accessor: TopologyAccessor, + + refresh_rate: Duration, + consecutive_failure_count: usize, +} + +impl TopologyRefresher { + pub fn new( + cfg: TopologyRefresherConfig, + topology_accessor: TopologyAccessor, + topology_provider: Box, + ) -> Self { + TopologyRefresher { + topology_provider, + topology_accessor, + refresh_rate: cfg.refresh_rate, + consecutive_failure_count: 0, + } + } + + pub fn change_topology_provider(&mut self, provider: Box) { + self.topology_provider = provider; + } + + pub async fn try_refresh(&mut self) { + trace!("Refreshing the topology"); + + if self.topology_accessor.controlled_manually() { + info!("topology is being controlled manually - we're going to wait until the control is released..."); + self.topology_accessor + .wait_for_released_manual_control() + .await; + } + + let new_topology = self.topology_provider.get_new_topology().await; + if new_topology.is_none() { + warn!("failed to obtain new network topology"); + } + + if new_topology.is_none() && self.consecutive_failure_count < MAX_FAILURE_COUNT { + // if we failed to grab this topology, but the one before it was alright, let's assume + // validator had a tiny hiccup and use the old data + warn!("we're going to keep on using the old topology for this iteration"); + self.consecutive_failure_count += 1; + return; + } else if new_topology.is_some() { + self.consecutive_failure_count = 0; + } + + self.topology_accessor + .update_global_topology(new_topology) + .await; + } + + pub async fn ensure_topology_is_routable(&self) -> Result<(), NymTopologyError> { + self.topology_accessor.ensure_is_routable().await + } + + pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) { + spawn_future(async move { + debug!("Started TopologyRefresher with graceful shutdown support"); + + #[cfg(not(target_arch = "wasm32"))] + let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval( + self.refresh_rate, + )); + + #[cfg(target_arch = "wasm32")] + let mut interval = + gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32); + + while !shutdown.is_shutdown() { + tokio::select! { + _ = interval.next() => { + self.try_refresh().await; + }, + _ = shutdown.recv() => { + log::trace!("TopologyRefresher: Received shutdown"); + }, + } + } + shutdown.recv_timeout().await; + log::debug!("TopologyRefresher: Exiting"); + }) + } +} diff --git a/clients/client-core/src/client/topology_control/nym_api_provider.rs b/clients/client-core/src/client/topology_control/nym_api_provider.rs new file mode 100644 index 0000000000..159387d921 --- /dev/null +++ b/clients/client-core/src/client/topology_control/nym_api_provider.rs @@ -0,0 +1,106 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; +use log::{error, warn}; +use nym_topology::provider_trait::TopologyProvider; +use nym_topology::{nym_topology_from_detailed, NymTopology, NymTopologyError}; +use rand::prelude::SliceRandom; +use rand::thread_rng; +use url::Url; + +pub(crate) struct NymApiTopologyProvider { + validator_client: validator_client::client::NymApiClient, + nym_api_urls: Vec, + + client_version: String, + currently_used_api: usize, +} + +impl NymApiTopologyProvider { + pub(crate) fn new(mut nym_api_urls: Vec, client_version: String) -> Self { + nym_api_urls.shuffle(&mut thread_rng()); + + NymApiTopologyProvider { + validator_client: validator_client::client::NymApiClient::new(nym_api_urls[0].clone()), + nym_api_urls, + client_version, + currently_used_api: 0, + } + } + + fn use_next_nym_api(&mut self) { + if self.nym_api_urls.len() == 1 { + warn!("There's only a single nym API available - it won't be possible to use a different one"); + return; + } + + self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len(); + self.validator_client + .change_nym_api(self.nym_api_urls[self.currently_used_api].clone()) + } + + /// Verifies whether nodes a reasonably distributed among all mix layers. + /// + /// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3. + /// However, this is a rather unrealistic expectation, instead we check whether there exists + /// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure. + /// + /// # Arguments + /// + /// * `topology`: active topology constructed from validator api data + fn check_layer_distribution( + &self, + active_topology: &NymTopology, + ) -> Result<(), NymTopologyError> { + let lower_threshold = 0.15; + let upper_threshold = 0.66; + active_topology.ensure_even_layer_distribution(lower_threshold, upper_threshold) + } + + async fn get_current_compatible_topology(&mut self) -> Option { + let mixnodes = match self.validator_client.get_cached_active_mixnodes().await { + Err(err) => { + error!("failed to get network mixnodes - {err}"); + return None; + } + Ok(mixes) => mixes, + }; + + let gateways = match self.validator_client.get_cached_gateways().await { + Err(err) => { + error!("failed to get network gateways - {err}"); + return None; + } + Ok(gateways) => gateways, + }; + + let topology = nym_topology_from_detailed(mixnodes, gateways) + .filter_system_version(&self.client_version); + + if let Err(err) = self.check_layer_distribution(&topology) { + warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used: {err}"); + self.use_next_nym_api(); + None + } else { + Some(topology) + } + } +} + +// hehe, wasm +#[cfg(not(target_arch = "wasm32"))] +#[async_trait] +impl TopologyProvider for NymApiTopologyProvider { + async fn get_new_topology(&mut self) -> Option { + self.get_current_compatible_topology().await + } +} + +#[cfg(target_arch = "wasm32")] +#[async_trait(?Send)] +impl TopologyProvider for NymApiTopologyProvider { + async fn get_new_topology(&mut self) -> Option { + self.get_current_compatible_topology().await + } +} diff --git a/clients/client-core/src/client/real_messages_control/real_traffic_stream/transmission_buffer.rs b/clients/client-core/src/client/transmission_buffer.rs similarity index 54% rename from clients/client-core/src/client/real_messages_control/real_traffic_stream/transmission_buffer.rs rename to clients/client-core/src/client/transmission_buffer.rs index 1f16f472c1..7686957fee 100644 --- a/clients/client-core/src/client/real_messages_control/real_traffic_stream/transmission_buffer.rs +++ b/clients/client-core/src/client/transmission_buffer.rs @@ -1,38 +1,57 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use client_connections::TransmissionLane; +use crate::client::helpers::{get_time_now, Instant}; +use crate::client::real_messages_control::real_traffic_stream::RealMessage; +use nym_sphinx::chunking::fragment::Fragment; +use nym_task::connections::TransmissionLane; use rand::{seq::SliceRandom, Rng}; use std::{ collections::{HashMap, HashSet, VecDeque}, time::Duration, }; -#[cfg(not(target_arch = "wasm32"))] -use tokio::time; - -#[cfg(target_arch = "wasm32")] -use wasm_timer; - -use super::{get_time_now, RealMessage}; - // The number of lanes included in the oldest set. Used when we need to prioritize traffic. const OLDEST_LANE_SET_SIZE: usize = 4; // As a way of prune connections we also check for timeouts. const MSG_CONSIDERED_STALE_AFTER_SECS: u64 = 10 * 60; -#[derive(Default)] -pub(crate) struct TransmissionBuffer { - buffer: HashMap, +pub(crate) trait SizedData { + fn data_size(&self) -> usize; } -impl TransmissionBuffer { +impl SizedData for RealMessage { + fn data_size(&self) -> usize { + self.packet_size() + } +} + +impl SizedData for Fragment { + fn data_size(&self) -> usize { + // note that raw `Fragment` is smaller than sphinx packet payload + // as it doesn't include surb-ack or the [shared] key materials + self.payload_size() + } +} + +#[derive(Default)] +pub(crate) struct TransmissionBuffer { + buffer: HashMap>, +} + +impl TransmissionBuffer { + pub(crate) fn new() -> Self { + TransmissionBuffer { + buffer: HashMap::new(), + } + } + #[allow(unused)] pub(crate) fn is_empty(&self) -> bool { self.buffer.is_empty() } - pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option { + pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option> { self.buffer.remove(lane) } @@ -57,20 +76,22 @@ impl TransmissionBuffer { .collect() } - #[cfg(not(target_arch = "wasm32"))] pub(crate) fn total_size(&self) -> usize { self.buffer.values().map(LaneBufferEntry::len).sum() } #[cfg(not(target_arch = "wasm32"))] - pub(crate) fn total_size_in_bytes(&self) -> usize { + pub(crate) fn total_size_in_bytes(&self) -> usize + where + T: SizedData, + { self.buffer .values() .map(|lane_buffer_entry| { lane_buffer_entry - .real_messages + .items .iter() - .map(|real_message| real_message.mix_packet.sphinx_packet().len()) + .map(|item| item.data_size()) .sum::() }) .sum() @@ -92,42 +113,51 @@ impl TransmissionBuffer { .collect() } - pub(crate) fn store(&mut self, lane: &TransmissionLane, real_messages: Vec) { + pub(crate) fn store>(&mut self, lane: &TransmissionLane, items: I) { if let Some(lane_buffer_entry) = self.buffer.get_mut(lane) { - lane_buffer_entry.append(real_messages); + lane_buffer_entry.extend(items); } else { self.buffer - .insert(*lane, LaneBufferEntry::new(real_messages)); + .insert(*lane, LaneBufferEntry::new(items.into_iter().collect())); } } - fn pick_random_lane(&self) -> Option<&TransmissionLane> { - let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect(); - lanes.choose(&mut rand::thread_rng()).copied() + pub(crate) fn store_multiple(&mut self, items: Vec<(TransmissionLane, T)>) { + for (lane, item) in items { + self.buffer + .entry(lane) + .or_insert_with(LaneBufferEntry::new_empty) + .push_item(item) + } } - fn pick_random_small_lane(&self) -> Option<&TransmissionLane> { + fn pick_random_lane(&self, rng: &mut R) -> Option<&TransmissionLane> { + let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect(); + lanes.choose(rng).copied() + } + + fn pick_random_small_lane(&self, rng: &mut R) -> Option<&TransmissionLane> { let lanes: Vec<&TransmissionLane> = self .buffer .iter() .filter(|(_, v)| v.is_small()) .map(|(k, _)| k) .collect(); - lanes.choose(&mut rand::thread_rng()).copied() + lanes.choose(rng).copied() } // 2/3 chance to pick from the old lanes - fn pick_random_old_lane(&self) -> Option { + fn pick_random_old_lane(&self, rng: &mut R) -> Option { let rand = &mut rand::thread_rng(); if rand.gen_ratio(2, 3) { let lanes = self.get_oldest_set(); lanes.choose(rand).copied() } else { - self.pick_random_lane().copied() + self.pick_random_lane(rng).copied() } } - fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option { + fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option { let real_msgs_queued = self.buffer.get_mut(lane)?; let real_next = real_msgs_queued.pop_front()?; real_msgs_queued.messages_transmitted += 1; @@ -137,19 +167,48 @@ impl TransmissionBuffer { Some(real_next) } - pub(crate) fn pop_next_message_at_random(&mut self) -> Option<(TransmissionLane, RealMessage)> { + pub(crate) fn pop_at_most_n_next_messages_at_random( + &mut self, + n: usize, + ) -> Option> { + // let start = Instant::now(); + + if self.buffer.is_empty() { + return None; + } + + let rng = &mut rand::thread_rng(); + let mut items = Vec::with_capacity(n); + + while items.len() < n { + let Some(next) = self.pop_next_message_at_random(rng) else { + break + }; + items.push(next) + } + + // todo!("time time taken"); + + Some(items) + } + + pub(crate) fn pop_next_message_at_random( + &mut self, + // turns out the caller always have access to some rng, so no point in instantiating new one + rng: &mut R, + ) -> Option<(TransmissionLane, T)> { if self.buffer.is_empty() { return None; } // Very basic heuristic where we prioritize according to small lanes first, the older lanes // to try to finish lanes when possible, then the rest. - let lane = if let Some(small_lane) = self.pick_random_small_lane() { + let lane = if let Some(small_lane) = self.pick_random_small_lane(rng) { *small_lane - } else if let Some(old_lane) = self.pick_random_old_lane() { + } else if let Some(old_lane) = self.pick_random_old_lane(rng) { old_lane } else { - *self.pick_random_lane()? + *self.pick_random_lane(rng)? }; let msg = self.pop_front_from_lane(&lane)?; @@ -171,35 +230,46 @@ impl TransmissionBuffer { } } -pub(crate) struct LaneBufferEntry { - pub real_messages: VecDeque, +pub(crate) struct LaneBufferEntry { + pub items: VecDeque, pub messages_transmitted: usize, - #[cfg(not(target_arch = "wasm32"))] - pub time_for_last_activity: time::Instant, - #[cfg(target_arch = "wasm32")] - pub time_for_last_activity: wasm_timer::Instant, + pub time_for_last_activity: Instant, } -impl LaneBufferEntry { - fn new(real_messages: Vec) -> Self { +impl LaneBufferEntry { + fn new_empty() -> Self { LaneBufferEntry { - real_messages: real_messages.into(), + items: VecDeque::new(), messages_transmitted: 0, time_for_last_activity: get_time_now(), } } - fn append(&mut self, real_messages: Vec) { - self.real_messages.append(&mut real_messages.into()); + fn new(items: VecDeque) -> Self { + LaneBufferEntry { + items, + messages_transmitted: 0, + time_for_last_activity: get_time_now(), + } + } + + fn push_item(&mut self, item: T) { + self.items.push_back(item); + // I'm not updating time here on purpose. This method is called just after `new_empty`, + // where the time is already set. Furthermore, this method is called there multiple times at once + } + + fn extend>(&mut self, items: I) { + self.items.extend(items); self.time_for_last_activity = get_time_now(); } - fn pop_front(&mut self) -> Option { - self.real_messages.pop_front() + fn pop_front(&mut self) -> Option { + self.items.pop_front() } fn is_small(&self) -> bool { - self.real_messages.len() < 100 + self.items.len() < 100 } fn is_stale(&self) -> bool { @@ -208,10 +278,10 @@ impl LaneBufferEntry { } fn len(&self) -> usize { - self.real_messages.len() + self.items.len() } fn is_empty(&self) -> bool { - self.real_messages.is_empty() + self.items.is_empty() } } diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs index 0ec88a8095..31c1da7168 100644 --- a/clients/client-core/src/config/mod.rs +++ b/clients/client-core/src/config/mod.rs @@ -1,8 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use config::{NymConfig, DB_FILE_NAME}; -use nymsphinx::params::PacketSize; +use nym_config::defaults::NymNetworkDetails; +use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME}; +use nym_sphinx::params::PacketSize; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use std::path::PathBuf; @@ -44,7 +45,8 @@ const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100; const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500; -const DEFAULT_MAXIMUM_REPLY_SURB_WAITING_PERIOD: Duration = Duration::from_secs(10); +const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10); +const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60); // 12 hours const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60); @@ -70,12 +72,15 @@ pub struct Config { #[serde(default)] debug: DebugConfig, } + impl ClientCoreConfigTrait for Config { fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig { &self.client.gateway_endpoint } } +impl OptionalSet for Config where T: NymConfig {} + impl Config { pub fn new>(id: S) -> Self where @@ -84,6 +89,7 @@ impl Config { Config::default().with_id(id) } + #[must_use] pub fn with_id>(mut self, id: S) -> Self where T: NymConfig, @@ -160,24 +166,47 @@ impl Config { changes_made } - pub fn with_disabled_credentials(&mut self, disabled_credentials_mode: bool) { + pub fn with_disabled_credentials(mut self, disabled_credentials_mode: bool) -> Self { self.client.disabled_credentials_mode = disabled_credentials_mode; + self } - pub fn with_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) { + pub fn set_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) { self.client.gateway_endpoint = gateway_endpoint; } + pub fn with_gateway_endpoint(mut self, gateway_endpoint: GatewayEndpointConfig) -> Self { + self.client.gateway_endpoint = gateway_endpoint; + self + } + pub fn with_gateway_id>(&mut self, id: S) { self.client.gateway_endpoint.gateway_id = id.into(); } - pub fn set_custom_validators(&mut self, validator_urls: Vec) { - self.client.validator_urls = validator_urls; + pub fn with_custom_nyxd(mut self, urls: Vec) -> Self { + self.client.nyxd_urls = urls; + self } - pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec) { - self.client.validator_api_urls = validator_api_urls; + pub fn set_custom_nyxd(&mut self, nyxd_urls: Vec) { + self.client.nyxd_urls = nyxd_urls; + } + + pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec) -> Self { + self.client.nym_api_urls = nym_api_urls; + self + } + + pub fn set_custom_nym_apis(&mut self, nym_api_urls: Vec) { + self.client.nym_api_urls = nym_api_urls; + } + + pub fn with_high_default_traffic_volume(mut self, enabled: bool) -> Self { + if enabled { + self.set_high_default_traffic_volume(); + } + self } pub fn set_high_default_traffic_volume(&mut self) { @@ -188,6 +217,13 @@ impl Config { self.debug.message_sending_average_delay = Duration::from_millis(4); } + pub fn with_disabled_cover_traffic(mut self, disabled: bool) -> Self { + if disabled { + self.set_no_cover_traffic() + } + self + } + pub fn set_no_cover_traffic(&mut self) { self.debug.disable_loop_cover_traffic_stream = true; self.debug.disable_main_poisson_packet_distribution = true; @@ -234,11 +270,11 @@ impl Config { } pub fn get_validator_endpoints(&self) -> Vec { - self.client.validator_urls.clone() + self.client.nyxd_urls.clone() } - pub fn get_validator_api_endpoints(&self) -> Vec { - self.client.validator_api_urls.clone() + pub fn get_nym_api_endpoints(&self) -> Vec { + self.client.nym_api_urls.clone() } pub fn get_gateway_id(&self) -> String { @@ -342,8 +378,12 @@ impl Config { self.debug.maximum_allowed_reply_surb_request_size } - pub fn get_maximum_reply_surb_waiting_period(&self) -> Duration { - self.debug.maximum_reply_surb_waiting_period + pub fn get_maximum_reply_surb_rerequest_waiting_period(&self) -> Duration { + self.debug.maximum_reply_surb_rerequest_waiting_period + } + + pub fn get_maximum_reply_surb_drop_waiting_period(&self) -> Duration { + self.debug.maximum_reply_surb_drop_waiting_period } pub fn get_maximum_reply_surb_age(&self) -> Duration { @@ -395,8 +435,8 @@ impl GatewayEndpointConfig { } } -impl From for GatewayEndpointConfig { - fn from(node: topology::gateway::Node) -> GatewayEndpointConfig { +impl From for GatewayEndpointConfig { + fn from(node: nym_topology::gateway::Node) -> GatewayEndpointConfig { let gateway_listener = node.clients_address(); GatewayEndpointConfig { gateway_id: node.identity_key.to_base58_string(), @@ -420,12 +460,13 @@ pub struct Client { #[serde(default)] disabled_credentials_mode: bool, - /// Addresses to nymd validators via which the client can communicate with the chain. - #[serde(default)] - validator_urls: Vec, + /// Addresses to nyxd validators via which the client can communicate with the chain. + #[serde(alias = "validator_urls")] + nyxd_urls: Vec, /// Addresses to APIs running on validator from which the client gets the view of the network. - validator_api_urls: Vec, + #[serde(alias = "validator_api_urls")] + nym_api_urls: Vec, /// Path to file containing private identity key. private_identity_key_file: PathBuf, @@ -470,13 +511,29 @@ pub struct Client { impl Default for Client { fn default() -> Self { + let network = NymNetworkDetails::new_mainnet(); + let nyxd_urls = network + .endpoints + .iter() + .map(|validator| validator.nyxd_url()) + .collect(); + let nym_api_urls = network + .endpoints + .iter() + .filter_map(|validator| validator.api_url()) + .collect::>(); + + if nym_api_urls.is_empty() { + panic!("we do not have any default nym-api urls available!") + } + // there must be explicit checks for whether id is not empty later Client { version: env!("CARGO_PKG_VERSION").to_string(), id: "".to_string(), disabled_credentials_mode: true, - validator_urls: vec![], - validator_api_urls: vec![], + nyxd_urls, + nym_api_urls, private_identity_key_file: Default::default(), public_identity_key_file: Default::default(), private_encryption_key_file: Default::default(), @@ -494,35 +551,35 @@ impl Default for Client { impl Client { fn default_private_identity_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("private_identity.pem") + T::default_data_directory(id).join("private_identity.pem") } fn default_public_identity_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("public_identity.pem") + T::default_data_directory(id).join("public_identity.pem") } fn default_private_encryption_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("private_encryption.pem") + T::default_data_directory(id).join("private_encryption.pem") } fn default_public_encryption_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("public_encryption.pem") + T::default_data_directory(id).join("public_encryption.pem") } fn default_gateway_shared_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("gateway_shared.pem") + T::default_data_directory(id).join("gateway_shared.pem") } fn default_ack_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("ack_key.pem") + T::default_data_directory(id).join("ack_key.pem") } fn default_reply_surb_database_path(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("persistent_reply_store.sqlite") + T::default_data_directory(id).join("persistent_reply_store.sqlite") } fn default_database_path(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join(DB_FILE_NAME) + T::default_data_directory(id).join(CRED_DB_FILE_NAME) } } @@ -616,7 +673,12 @@ pub struct DebugConfig { /// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking /// for more even though in theory they wouldn't need to. #[serde(with = "humantime_serde")] - pub maximum_reply_surb_waiting_period: Duration, + pub maximum_reply_surb_rerequest_waiting_period: Duration, + + /// Defines maximum amount of time the client is going to wait for reply surbs before + /// deciding it's never going to get them and would drop all pending messages + #[serde(with = "humantime_serde")] + pub maximum_reply_surb_drop_waiting_period: Duration, /// Defines maximum amount of time given reply surb is going to be valid for. /// This is going to be superseded by key rotation once implemented. @@ -635,6 +697,17 @@ pub enum ExtendedPacketSize { Extended8, Extended16, Extended32, + Extended10, + Extended15, + Extended20, + Extended25, + Extended50, + Extended100, + Extended150, + Extended200, + Extended250, + Extended500, + } impl Default for DebugConfig { @@ -657,7 +730,9 @@ impl Default for DebugConfig { minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, - maximum_reply_surb_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_WAITING_PERIOD, + maximum_reply_surb_rerequest_waiting_period: + DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD, + 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, } @@ -670,6 +745,17 @@ impl From for PacketSize { ExtendedPacketSize::Extended8 => PacketSize::ExtendedPacket8, ExtendedPacketSize::Extended16 => PacketSize::ExtendedPacket16, ExtendedPacketSize::Extended32 => PacketSize::ExtendedPacket32, + ExtendedPacketSize::Extended10 => PacketSize::ExtendedPacket10, + ExtendedPacketSize::Extended15 => PacketSize::ExtendedPacket15, + ExtendedPacketSize::Extended20 => PacketSize::ExtendedPacket20, + ExtendedPacketSize::Extended25 => PacketSize::ExtendedPacket25, + ExtendedPacketSize::Extended50 => PacketSize::ExtendedPacket50, + ExtendedPacketSize::Extended100 => PacketSize::ExtendedPacket100, + ExtendedPacketSize::Extended150 => PacketSize::ExtendedPacket150, + ExtendedPacketSize::Extended200 => PacketSize::ExtendedPacket200, + ExtendedPacketSize::Extended250 => PacketSize::ExtendedPacket250, + ExtendedPacketSize::Extended500 => PacketSize::ExtendedPacket500, } } } + diff --git a/clients/client-core/src/config/persistence/key_pathfinder.rs b/clients/client-core/src/config/persistence/key_pathfinder.rs index d5dda75a27..97d5fd3727 100644 --- a/clients/client-core/src/config/persistence/key_pathfinder.rs +++ b/clients/client-core/src/config/persistence/key_pathfinder.rs @@ -2,17 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::Config; -use config::NymConfig; +use nym_config::NymConfig; use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct ClientKeyPathfinder { - identity_private_key: PathBuf, - identity_public_key: PathBuf, - encryption_private_key: PathBuf, - encryption_public_key: PathBuf, - gateway_shared_key: PathBuf, - ack_key: PathBuf, + pub identity_private_key: PathBuf, + pub identity_public_key: PathBuf, + pub encryption_private_key: PathBuf, + pub encryption_public_key: PathBuf, + pub gateway_shared_key: PathBuf, + pub ack_key: PathBuf, } impl ClientKeyPathfinder { @@ -22,8 +22,8 @@ impl ClientKeyPathfinder { ClientKeyPathfinder { identity_private_key: config_dir.join("private_identity.pem"), identity_public_key: config_dir.join("public_identity.pem"), - encryption_private_key: config_dir.join("public_encryption.pem"), - encryption_public_key: config_dir.join("private_encryption.pem"), + encryption_private_key: config_dir.join("private_encryption.pem"), + encryption_public_key: config_dir.join("public_encryption.pem"), gateway_shared_key: config_dir.join("gateway_shared.pem"), ack_key: config_dir.join("ack_key.pem"), } @@ -40,6 +40,28 @@ impl ClientKeyPathfinder { } } + pub fn any_file_exists(&self) -> bool { + matches!(self.identity_public_key.try_exists(), Ok(true)) + || matches!(self.identity_private_key.try_exists(), Ok(true)) + || matches!(self.encryption_public_key.try_exists(), Ok(true)) + || matches!(self.encryption_private_key.try_exists(), Ok(true)) + || matches!(self.gateway_shared_key.try_exists(), Ok(true)) + || matches!(self.ack_key.try_exists(), Ok(true)) + } + + pub fn any_file_exists_and_return(&self) -> Option { + file_exists(&self.identity_public_key) + .or_else(|| file_exists(&self.identity_private_key)) + .or_else(|| file_exists(&self.encryption_public_key)) + .or_else(|| file_exists(&self.encryption_private_key)) + .or_else(|| file_exists(&self.gateway_shared_key)) + .or_else(|| file_exists(&self.ack_key)) + } + + pub fn gateway_key_file_exists(&self) -> bool { + matches!(self.gateway_shared_key.try_exists(), Ok(true)) + } + pub fn private_identity_key(&self) -> &Path { &self.identity_private_key } @@ -64,3 +86,10 @@ impl ClientKeyPathfinder { &self.ack_key } } + +fn file_exists(path: &Path) -> Option { + if matches!(path.try_exists(), Ok(true)) { + return Some(path.to_path_buf()); + } + None +} diff --git a/clients/client-core/src/error.rs b/clients/client-core/src/error.rs index 3b179567d1..4c9e9e5b34 100644 --- a/clients/client-core/src/error.rs +++ b/clients/client-core/src/error.rs @@ -1,14 +1,14 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::client::replies::reply_storage::ReplyStorageBackend; -use crypto::asymmetric::identity::Ed25519RecoveryError; use gateway_client::error::GatewayClientError; -use topology::NymTopologyError; +use nym_crypto::asymmetric::identity::Ed25519RecoveryError; +use nym_topology::gateway::GatewayConversionError; +use nym_topology::NymTopologyError; use validator_client::ValidatorClientError; #[derive(thiserror::Error, Debug)] -pub enum ClientCoreError { +pub enum ClientCoreError { #[error("I/O error: {0}")] IoError(#[from] std::io::Error), @@ -30,8 +30,8 @@ pub enum ClientCoreError { #[error("Failed to setup gateway")] FailedToSetupGateway, - #[error("List of validator apis is empty")] - ListOfValidatorApisIsEmpty, + #[error("List of nym apis is empty")] + ListOfNymApisIsEmpty, #[error("Could not load existing gateway configuration: {0}")] CouldNotLoadExistingGatewayConfiguration(std::io::Error), @@ -40,7 +40,9 @@ pub enum ClientCoreError { InsufficientNetworkTopology(#[from] NymTopologyError), #[error("experienced a failure with our reply surb persistent storage: {source}")] - SurbStorageError { source: B::StorageError }, + SurbStorageError { + source: Box, + }, #[error("The gateway id is invalid - {0}")] UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError), @@ -52,7 +54,35 @@ pub enum ClientCoreError { GatewayOwnerUnknown, #[error("The address of the gateway is unknown - did you run init?")] - GatwayAddressUnknown, + GatewayAddressUnknown, + + #[error("The gateway is malformed: {source}")] + MalformedGateway { + #[from] + source: GatewayConversionError, + }, + + #[error("failed to establish connection to gateway: {source}")] + GatewayConnectionFailure { + #[from] + source: tungstenite::Error, + }, + + #[cfg(target_arch = "wasm32")] + #[error("failed to establish gateway connection (wasm)")] + GatewayJsConnectionFailure, + + #[error("Gateway connection was abruptly closed")] + GatewayConnectionAbruptlyClosed, + + #[error("Timed out while trying to establish gateway connection")] + GatewayConnectionTimeout, + + #[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")] UnexpectedExit, @@ -61,8 +91,10 @@ pub enum ClientCoreError { /// Set of messages that the client can send to listeners via the task manager #[derive(thiserror::Error, Debug)] pub enum ClientCoreStatusMessage { + // NOTE: The nym-connect frontend listens for these strings, so don't change them until we have a more robust mechanism in place #[error("The connected gateway is slow, or the connection to it is slow")] GatewayIsSlow, + // NOTE: The nym-connect frontend listens for these strings, so don't change them until we have a more robust mechanism in place #[error("The connected gateway is very slow, or the connection to it is very slow")] GatewayIsVerySlow, } diff --git a/clients/client-core/src/init/helpers.rs b/clients/client-core/src/init/helpers.rs index 7947d614a1..b3f5e1c46d 100644 --- a/clients/client-core/src/init/helpers.rs +++ b/clients/client-core/src/init/helpers.rs @@ -1,72 +1,239 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::client::replies::reply_storage::ReplyStorageBackend; use crate::{ client::key_manager::KeyManager, config::{persistence::key_pathfinder::ClientKeyPathfinder, Config}, error::ClientCoreError, }; -use config::NymConfig; -use crypto::asymmetric::identity; +use futures::{SinkExt, StreamExt}; use gateway_client::GatewayClient; use gateway_requests::registration::handshake::SharedKeys; -use rand::{rngs::OsRng, seq::SliceRandom, thread_rng}; +use log::{debug, info, trace, warn}; +use nym_config::NymConfig; +use nym_crypto::asymmetric::identity; +use nym_topology::{filter::VersionFilterable, gateway}; +use rand::{seq::SliceRandom, thread_rng, Rng}; use std::{sync::Arc, time::Duration}; use tap::TapFallible; -use topology::{filter::VersionFilterable, gateway}; +use tungstenite::Message; use url::Url; -pub(super) async fn query_gateway_details( - validator_servers: Vec, - chosen_gateway_id: Option, -) -> Result> -where - B: ReplyStorageBackend, -{ - let validator_api = validator_servers - .choose(&mut thread_rng()) - .ok_or(ClientCoreError::ListOfValidatorApisIsEmpty)?; - let validator_client = validator_client::client::ApiClient::new(validator_api.clone()); +#[cfg(not(target_arch = "wasm32"))] +use tokio::net::TcpStream; +#[cfg(not(target_arch = "wasm32"))] +use tokio::time::Instant; +#[cfg(not(target_arch = "wasm32"))] +use tokio_tungstenite::connect_async; +#[cfg(not(target_arch = "wasm32"))] +use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; +#[cfg(not(target_arch = "wasm32"))] +use validator_client::nyxd::SigningNyxdClient; - log::trace!("Fetching list of gateways from: {}", validator_api); - let gateways = validator_client.get_cached_gateways().await?; +#[cfg(not(target_arch = "wasm32"))] +type WsConn = WebSocketStream>; + +#[cfg(target_arch = "wasm32")] +use gateway_client::wasm_mockups::SigningNyxdClient; +#[cfg(target_arch = "wasm32")] +use wasm_timer::Instant; +#[cfg(target_arch = "wasm32")] +use wasm_utils::websocket::JSWebsocket; + +#[cfg(target_arch = "wasm32")] +type WsConn = JSWebsocket; + +const MEASUREMENTS: usize = 3; + +#[cfg(not(target_arch = "wasm32"))] +const CONN_TIMEOUT: Duration = Duration::from_millis(1500); +const PING_TIMEOUT: Duration = Duration::from_millis(1000); + +struct GatewayWithLatency { + gateway: gateway::Node, + latency: Duration, +} + +impl GatewayWithLatency { + fn new(gateway: gateway::Node, latency: Duration) -> Self { + GatewayWithLatency { gateway, latency } + } +} + +async fn current_gateways( + rng: &mut R, + nym_apis: Vec, +) -> Result, ClientCoreError> { + let nym_api = nym_apis + .choose(rng) + .ok_or(ClientCoreError::ListOfNymApisIsEmpty)?; + let client = validator_client::client::NymApiClient::new(nym_api.clone()); + + log::trace!("Fetching list of gateways from: {}", nym_api); + + let gateways = client.get_cached_gateways().await?; let valid_gateways = gateways .into_iter() .filter_map(|gateway| gateway.try_into().ok()) .collect::>(); + // 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")); + Ok(filtered_gateways) +} - // if we have chosen particular gateway - use it, otherwise choose a random one. - // (remember that in active topology all gateways have at least 100 reputation so should - // be working correctly) - if let Some(gateway_id) = chosen_gateway_id { - filtered_gateways - .iter() - .find(|gateway| gateway.identity_key.to_base58_string() == gateway_id) - .ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_id.to_string())) - .cloned() - } else { - filtered_gateways - .choose(&mut rand::thread_rng()) - .ok_or(ClientCoreError::NoGatewaysOnNetwork) - .cloned() +#[cfg(not(target_arch = "wasm32"))] +async fn connect(endpoint: &str) -> Result { + match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await { + Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout), + Ok(Err(conn_failure)) => Err(conn_failure.into()), + Ok(Ok((stream, _))) => Ok(stream), } } -async fn register_with_gateway( +#[cfg(target_arch = "wasm32")] +async fn connect(endpoint: &str) -> Result { + JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure) +} + +async fn measure_latency(gateway: gateway::Node) -> Result { + let addr = gateway.clients_address(); + trace!( + "establishing connection to {} ({addr})...", + gateway.identity_key, + ); + let mut stream = connect(&addr).await?; + + let mut results = Vec::new(); + for _ in 0..MEASUREMENTS { + let measurement_future = async { + let ping_content = vec![1, 2, 3]; + let start = Instant::now(); + stream.send(Message::Ping(ping_content.clone())).await?; + + match stream.next().await { + Some(Ok(Message::Pong(content))) => { + if content == ping_content { + let elapsed = Instant::now().duration_since(start); + trace!("current ping time: {elapsed:?}"); + results.push(elapsed); + } else { + warn!("received a pong message with different content? wtf.") + } + } + Some(Ok(_)) => warn!("received a message that's not a pong!"), + Some(Err(err)) => return Err(err.into()), + None => return Err(ClientCoreError::GatewayConnectionAbruptlyClosed), + } + + Ok::<(), ClientCoreError>(()) + }; + + // thanks to wasm we can't use tokio::time::timeout : ( + #[cfg(not(target_arch = "wasm32"))] + let timeout = tokio::time::sleep(PING_TIMEOUT); + #[cfg(not(target_arch = "wasm32"))] + tokio::pin!(timeout); + + #[cfg(target_arch = "wasm32")] + let mut timeout = wasm_timer::Delay::new(PING_TIMEOUT); + + tokio::select! { + _ = &mut timeout => { + warn!("timed out while trying to perform measurement...") + } + res = measurement_future => res?, + } + } + + let count = results.len() as u64; + if count == 0 { + return Err(ClientCoreError::NoGatewayMeasurements { + identity: gateway.identity_key.to_base58_string(), + }); + } + + let sum: Duration = results.into_iter().sum(); + let avg = Duration::from_nanos(sum.as_nanos() as u64 / count); + + Ok(GatewayWithLatency::new(gateway, avg)) +} + +async fn choose_gateway_by_latency( + rng: &mut R, + gateways: Vec, +) -> Result { + info!("choosing gateway by latency..."); + + let mut gateways_with_latency = Vec::new(); + for gateway in gateways { + let id = *gateway.identity(); + trace!("measuring latency to {id}..."); + let with_latency = match measure_latency(gateway).await { + Ok(res) => res, + Err(err) => { + warn!("failed to measure {id}: {err}"); + continue; + } + }; + debug!( + "{id} ({}): {:?}", + with_latency.gateway.location, with_latency.latency + ); + gateways_with_latency.push(with_latency) + } + + let chosen = gateways_with_latency + .choose_weighted(rng, |item| 1. / item.latency.as_secs_f32()) + .expect("invalid selection weight!"); + + info!( + "chose gateway {} (located at {}) with average latency of {:?}", + chosen.gateway.identity_key, chosen.gateway.location, chosen.latency + ); + + Ok(chosen.gateway.clone()) +} + +fn uniformly_random_gateway( + rng: &mut R, + gateways: Vec, +) -> Result { + gateways + .choose(rng) + .ok_or(ClientCoreError::NoGatewaysOnNetwork) + .cloned() +} + +pub(super) async fn query_gateway_details( + validator_servers: Vec, + chosen_gateway_id: Option, + by_latency: bool, +) -> Result { + let mut rng = thread_rng(); + let gateways = current_gateways(&mut rng, validator_servers).await?; + + // if we set an explicit gateway, use that one and nothing else + if let Some(explicitly_chosen) = chosen_gateway_id { + gateways + .into_iter() + .find(|gateway| gateway.identity_key == explicitly_chosen) + .ok_or_else(|| ClientCoreError::NoGatewayWithId(explicitly_chosen.to_string())) + } else if by_latency { + choose_gateway_by_latency(&mut rng, gateways).await + } else { + uniformly_random_gateway(&mut rng, gateways) + } +} + +pub(super) async fn register_with_gateway( gateway: &gateway::Node, our_identity: Arc, -) -> Result, ClientCoreError> -where - B: ReplyStorageBackend, -{ +) -> Result, ClientCoreError> { let timeout = Duration::from_millis(1500); - let mut gateway_client = GatewayClient::new_init( + let mut gateway_client: GatewayClient = GatewayClient::new_init( gateway.clients_address(), gateway.identity_key, - gateway.owner.clone(), our_identity.clone(), timeout, ); @@ -81,21 +248,13 @@ where Ok(shared_keys) } -pub(super) async fn register_with_gateway_and_store_keys( - gateway_details: gateway::Node, +pub(super) fn store_keys( + key_manager: &KeyManager, config: &Config, -) -> Result<(), ClientCoreError> +) -> Result<(), ClientCoreError> where T: NymConfig, - B: ReplyStorageBackend, { - let mut rng = OsRng; - let mut key_manager = KeyManager::new(&mut rng); - - let shared_keys = - register_with_gateway(&gateway_details, key_manager.identity_keypair()).await?; - key_manager.insert_gateway_shared_key(shared_keys); - let pathfinder = ClientKeyPathfinder::new_from_config(config); Ok(key_manager .store_keys(&pathfinder) diff --git a/clients/client-core/src/init/mod.rs b/clients/client-core/src/init/mod.rs index cdf210a27e..6e1700d7ae 100644 --- a/clients/client-core/src/init/mod.rs +++ b/clients/client-core/src/init/mod.rs @@ -5,25 +5,27 @@ use std::fmt::Display; -use nymsphinx::addressing::{clients::Recipient, nodes::NodeIdentity}; +use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity}; +use rand::rngs::OsRng; use serde::Serialize; use tap::TapFallible; -use config::NymConfig; -use crypto::asymmetric::{encryption, identity}; +use nym_config::NymConfig; +use nym_crypto::asymmetric::{encryption, identity}; +use url::Url; -use crate::client::replies::reply_storage::ReplyStorageBackend; +use crate::client::key_manager::KeyManager; use crate::{ config::{ persistence::key_pathfinder::ClientKeyPathfinder, ClientCoreConfigTrait, Config, GatewayEndpointConfig, }, error::ClientCoreError, - init::helpers::{query_gateway_details, register_with_gateway_and_store_keys}, }; mod helpers; +/// Struct describing the results of the client initialization procedure. #[derive(Debug, Serialize)] pub struct InitResults { version: String, @@ -61,84 +63,99 @@ impl Display for InitResults { } } -/// Convenience function for setting up the gateway for a client. Depending on the arguments given -/// it will do the sensible thing. -pub async fn setup_gateway( +/// Create a new set of client keys. +pub fn new_client_keys() -> KeyManager { + let mut rng = OsRng; + KeyManager::new(&mut rng) +} + +/// Authenticate and register with a gateway. +/// Either pick one at random by querying the available gateways from the nym-api, or use the +/// chosen one if it's among the available ones. +/// The shared key is added to the supplied `KeyManager` and the endpoint details are returned. +pub async fn register_with_gateway( + key_manager: &mut KeyManager, + nym_api_endpoints: Vec, + chosen_gateway_id: Option, + by_latency: bool, +) -> Result { + // Get the gateway details of the gateway we will use + let gateway = + helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id, by_latency).await?; + log::debug!("Querying gateway gives: {}", gateway); + + let our_identity = key_manager.identity_keypair(); + + // Establish connection, authenticate and generate keys for talking with the gateway + let shared_keys = helpers::register_with_gateway(&gateway, our_identity).await?; + key_manager.insert_gateway_shared_key(shared_keys); + + Ok(gateway.into()) +} + +/// Convenience function for setting up the gateway for a client given a `Config`. Depending on the +/// arguments given it will do the sensible thing. Either it will +/// +/// a. Reuse existing gateway configuration from storage. +/// b. Create a new gateway configuration but keep existing keys. This assumes that the caller +/// knows what they are doing and that the keys match the requested gateway. +/// c. Create a new gateway configuration with a newly registered gateway and keys. +pub async fn setup_gateway_from_config( register_gateway: bool, - user_chosen_gateway_id: Option, + user_chosen_gateway_id: Option, config: &Config, -) -> Result> + by_latency: bool, +) -> Result where - B: ReplyStorageBackend, C: NymConfig + ClientCoreConfigTrait, T: NymConfig, { let id = config.get_id(); - if register_gateway { - register_with_gateway(user_chosen_gateway_id, config).await - } else if let Some(user_chosen_gateway_id) = user_chosen_gateway_id { - config_gateway_with_existing_keys(user_chosen_gateway_id, config).await - } else { - reuse_existing_gateway_config::(&id) + + // If we are not going to register gateway, and an explicitly chosed gateway is not passed in, + // load the existing configuration file + if !register_gateway && user_chosen_gateway_id.is_none() { + println!("Not registering gateway, will reuse existing config and keys"); + return load_existing_gateway_config::(&id); } -} -/// Get the gateway details by querying the validator-api. Either pick one at random or use -/// the chosen one if it's among the available ones. -/// Saves keys to disk, specified by the paths in `config`. -pub async fn register_with_gateway( - user_chosen_gateway_id: Option, - config: &Config, -) -> Result> -where - B: ReplyStorageBackend, - T: NymConfig, -{ - println!("Configuring gateway"); - let gateway = - query_gateway_details(config.get_validator_api_endpoints(), user_chosen_gateway_id).await?; - log::debug!("Querying gateway gives: {}", gateway); - - // Registering with gateway by setting up and writing shared keys to disk - log::trace!("Registering gateway"); - register_with_gateway_and_store_keys(gateway.clone(), config).await?; - println!("Saved all generated keys"); - - Ok(gateway.into()) -} - -/// Set the gateway using the usual procedue of querying the validator-api, but don't register or -/// create any keys. -/// This assumes that the user knows what they are doing, and that the existing keys are valid for -/// the gateway being used -pub async fn config_gateway_with_existing_keys( - user_chosen_gateway_id: String, - config: &Config, -) -> Result> -where - B: ReplyStorageBackend, - T: NymConfig, -{ - println!("Using gateway provided by user, keeping existing keys"); - let gateway = query_gateway_details( - config.get_validator_api_endpoints(), - Some(user_chosen_gateway_id), + // Else, we preceed by querying the nym-api + let gateway = helpers::query_gateway_details( + config.get_nym_api_endpoints(), + user_chosen_gateway_id, + by_latency, ) .await?; log::debug!("Querying gateway gives: {}", gateway); + + // If we are not registering, just return this and assume the caller has the keys already and + // wants to keep the, + if !register_gateway && user_chosen_gateway_id.is_some() { + println!("Using gateway provided by user, keeping existing keys"); + return Ok(gateway.into()); + } + + // Create new keys and derive our identity + let mut key_manager = new_client_keys(); + let our_identity = key_manager.identity_keypair(); + + // Establish connection, authenticate and generate keys for talking with the gateway + println!("Registering with new gateway"); + let shared_keys = helpers::register_with_gateway(&gateway, our_identity).await?; + key_manager.insert_gateway_shared_key(shared_keys); + + // Write all keys to storage and just return the gateway endpoint config. It is assumed that we + // will load keys from storage when actually connecting. + helpers::store_keys(&key_manager, config)?; Ok(gateway.into()) } /// Read and reuse the existing gateway configuration from a file that was generate earlier. -pub fn reuse_existing_gateway_config( - id: &str, -) -> Result> +pub fn load_existing_gateway_config(id: &str) -> Result where - B: ReplyStorageBackend, T: NymConfig + ClientCoreConfigTrait, { - println!("Not registering gateway, will reuse existing config and keys"); - T::load_from_file(Some(id)) + T::load_from_file(id) .map(|existing_config| existing_config.get_gateway_endpoint().clone()) .map_err(|err| { log::error!( @@ -152,22 +169,32 @@ where }) } +/// Get the full client address from the client keys and the gateway identity +pub fn get_client_address( + key_manager: &KeyManager, + gateway_config: &GatewayEndpointConfig, +) -> Recipient { + Recipient::new( + *key_manager.identity_keypair().public_key(), + *key_manager.encryption_keypair().public_key(), + // TODO: below only works under assumption that gateway address == gateway id + // (which currently is true) + NodeIdentity::from_base58_string(&gateway_config.gateway_id).unwrap(), + ) +} + /// Get the client address by loading the keys from stored files. -pub fn get_client_address_from_stored_keys( +pub fn get_client_address_from_stored_keys( config: &Config, -) -> Result> +) -> Result where - T: config::NymConfig, - B: ReplyStorageBackend, + T: nym_config::NymConfig, { - fn load_identity_keys( + fn load_identity_keys( pathfinder: &ClientKeyPathfinder, - ) -> Result> - where - B: ReplyStorageBackend, - { + ) -> Result { let identity_keypair: identity::KeyPair = - pemstore::load_keypair(&pemstore::KeyPairPath::new( + nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( pathfinder.private_identity_key().to_owned(), pathfinder.public_identity_key().to_owned(), )) @@ -175,14 +202,11 @@ where Ok(identity_keypair) } - fn load_sphinx_keys( + fn load_sphinx_keys( pathfinder: &ClientKeyPathfinder, - ) -> Result> - where - B: ReplyStorageBackend, - { + ) -> Result { let sphinx_keypair: encryption::KeyPair = - pemstore::load_keypair(&pemstore::KeyPairPath::new( + nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( pathfinder.private_encryption_key().to_owned(), pathfinder.public_encryption_key().to_owned(), )) @@ -208,9 +232,9 @@ where pub fn output_to_json(init_results: &T, output_file: &str) { match std::fs::File::create(output_file) { Ok(file) => match serde_json::to_writer_pretty(file, init_results) { - Ok(_) => println!("Saved: {}", output_file), - Err(err) => eprintln!("Could not save {}: {}", output_file, err), + Ok(_) => println!("Saved: {output_file}"), + Err(err) => eprintln!("Could not save {output_file}: {err}"), }, - Err(err) => eprintln!("Could not save {}: {}", output_file, err), + Err(err) => eprintln!("Could not save {output_file}: {err}"), } } diff --git a/clients/credential/Cargo.toml b/clients/credential/Cargo.toml index ccf241b1cb..ca836f3858 100644 --- a/clients/credential/Cargo.toml +++ b/clients/credential/Cargo.toml @@ -7,23 +7,21 @@ edition = "2021" [dependencies] bip39 = "1.0.1" -cfg-if = "0.1" -clap = { version = "3.2", features = ["cargo", "derive"] } +clap = { version = "4.0", features = ["cargo", "derive"] } +log = "0.4" rand = "0.7.3" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" url = "2.2" -tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime +tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime -coconut-interface = { path = "../../common/coconut-interface" } -config = { path = "../../common/config" } -completions = { path = "../../common/completions" } -credentials = { path = "../../common/credentials" } -credential-storage = { path = "../../common/credential-storage" } -crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] } -network-defaults = { path = "../../common/network-defaults" } -pemstore = { path = "../../common/pemstore" } -validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] } +nym-coconut-interface = { path = "../../common/coconut-interface" } +nym-config = { path = "../../common/config" } +nym-credentials = { path = "../../common/credentials" } +nym-credential-storage = { path = "../../common/credential-storage" } +nym-crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] } +nym-bin-common = { path = "../../common/bin-common"} +nym-network-defaults = { path = "../../common/network-defaults" } +nym-pemstore = { path = "../../common/pemstore" } +validator-client = { path = "../../common/client-libs/validator-client", features = ["nyxd-client"] } -[features] -coconut = ["credentials/coconut"] diff --git a/clients/credential/src/client.rs b/clients/credential/src/client.rs index 0eb63bf340..7e947a5b74 100644 --- a/clients/credential/src/client.rs +++ b/clients/credential/src/client.rs @@ -3,30 +3,30 @@ use crate::error::Result; use bip39::Mnemonic; -use network_defaults::{NymNetworkDetails, VOUCHER_INFO}; +use nym_network_defaults::{NymNetworkDetails, VOUCHER_INFO}; use std::str::FromStr; use url::Url; -use validator_client::nymd; -use validator_client::nymd::traits::CoconutBandwidthSigningClient; -use validator_client::nymd::{Coin, Fee, NymdClient, SigningNymdClient}; +use validator_client::nyxd; +use validator_client::nyxd::traits::CoconutBandwidthSigningClient; +use validator_client::nyxd::{Coin, Fee, NyxdClient, SigningNyxdClient}; pub(crate) struct Client { - nymd_client: NymdClient, + nyxd_client: NyxdClient, mix_denom_base: String, } impl Client { - pub fn new(nymd_url: &str, mnemonic: &str) -> Self { - let nymd_url = Url::from_str(nymd_url).unwrap(); + pub fn new(nyxd_url: &str, mnemonic: &str) -> Self { + let nyxd_url = Url::from_str(nyxd_url).unwrap(); let mnemonic = Mnemonic::from_str(mnemonic).unwrap(); let network_details = NymNetworkDetails::new_from_env(); - let config = nymd::Config::try_from_nym_network_details(&network_details) + let config = nyxd::Config::try_from_nym_network_details(&network_details) .expect("failed to construct valid validator client config with the provided network"); - let nymd_client = - NymdClient::connect_with_mnemonic(config, nymd_url.as_ref(), mnemonic, None).unwrap(); + let nyxd_client = + NyxdClient::connect_with_mnemonic(config, nyxd_url.as_ref(), mnemonic, None).unwrap(); Client { - nymd_client, + nyxd_client, mix_denom_base: network_details.chain_details.mix_denom.base, } } @@ -40,7 +40,7 @@ impl Client { ) -> Result { let amount = Coin::new(amount as u128, self.mix_denom_base.clone()); Ok(self - .nymd_client + .nyxd_client .deposit( amount, String::from(VOUCHER_INFO), diff --git a/clients/credential/src/commands.rs b/clients/credential/src/commands.rs index 106b2c318e..fe58078f54 100644 --- a/clients/credential/src/commands.rs +++ b/clients/credential/src/commands.rs @@ -1,28 +1,32 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use clap::{Args, Subcommand}; -use completions::ArgShell; +use clap::{ArgGroup, Args, Subcommand}; +use log::*; +use nym_bin_common::completions::ArgShell; use rand::rngs::OsRng; use std::str::FromStr; -use coconut_interface::{Base58, Parameters}; -use credential_storage::storage::Storage; -use credential_storage::PersistentStorage; -use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES}; -use credentials::coconut::utils::obtain_aggregate_signature; -use crypto::asymmetric::{encryption, identity}; -use network_defaults::{NymNetworkDetails, VOUCHER_INFO}; -use validator_client::nymd::tx::Hash; -use validator_client::{CoconutApiClient, Config}; +use nym_coconut_interface::{Base58, Parameters}; +use nym_credential_storage::storage::Storage; +use nym_credential_storage::PersistentStorage; +use nym_credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES}; +use nym_credentials::coconut::utils::obtain_aggregate_signature; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_network_defaults::VOUCHER_INFO; +use validator_client::nyxd::traits::DkgQueryClient; +use validator_client::nyxd::tx::Hash; +use validator_client::nyxd::CosmWasmClient; +use validator_client::CoconutApiClient; use crate::client::Client; use crate::error::{CredentialClientError, Result}; +use crate::recovery_storage::RecoveryStorage; use crate::state::{KeyPair, State}; #[derive(Subcommand)] pub(crate) enum Command { - /// Run the binary + /// Run the binary to obtain a credential Run(Run), /// Generate shell completions @@ -33,30 +37,45 @@ pub(crate) enum Command { } #[derive(Args)] +#[clap(group( +ArgGroup::new("recov") +.required(true) +.args(&["amount", "recovery_mode"]), +))] pub(crate) struct Run { /// Home directory of the client that is supposed to use the credential. #[clap(long)] pub(crate) client_home_directory: std::path::PathBuf, - /// The nymd URL that should be used + /// The nyxd URL that should be used #[clap(long)] - pub(crate) nymd_url: String, + pub(crate) nyxd_url: String, /// A mnemonic for the account that buys the credential #[clap(long)] pub(crate) mnemonic: String, - /// The amount of utokens the credential will hold - #[clap(long)] + /// The amount of utokens the credential will hold. If recovery mode is enabled, this value + /// is not needed + #[clap(long, default_value = "0")] pub(crate) amount: u64, + + /// Path to a directory used to store recovery files for unconsumed deposits + #[clap(long)] + pub(crate) recovery_dir: std::path::PathBuf, + + /// Recovery mode, when enabled, tries to recover any deposit data dumped in recovery_dir + #[clap(long)] + pub(crate) recovery_mode: bool, } -pub(crate) async fn deposit(nymd_url: &str, mnemonic: &str, amount: u64) -> Result { +pub(crate) async fn deposit(nyxd_url: &str, mnemonic: &str, amount: u64) -> Result { 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 client = Client::new(nymd_url, mnemonic); + let client = Client::new(nyxd_url, mnemonic); let tx_hash = client .deposit( amount, @@ -66,48 +85,81 @@ pub(crate) async fn deposit(nymd_url: &str, mnemonic: &str, amount: u64) -> Resu ) .await?; - let state = State { - amount, - tx_hash, - signing_keypair, - encryption_keypair, - }; + let voucher = BandwidthVoucher::new( + ¶ms, + amount.to_string(), + VOUCHER_INFO.to_string(), + Hash::from_str(&tx_hash).map_err(|_| CredentialClientError::InvalidTxHash)?, + identity::PrivateKey::from_base58_string(&signing_keypair.private_key)?, + encryption::PrivateKey::from_base58_string(&encryption_keypair.private_key)?, + ); + + let state = State { voucher, params }; Ok(state) } -pub(crate) async fn get_credential(state: &State, shared_storage: PersistentStorage) -> Result<()> { - let network_details = NymNetworkDetails::new_from_env(); - let config = Config::try_from_nym_network_details(&network_details)?; - let client = validator_client::Client::new_query(config)?; - let coconut_api_clients = CoconutApiClient::all_coconut_api_clients(&client).await?; - - let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap(); - let bandwidth_credential_attributes = BandwidthVoucher::new( - ¶ms, - state.amount.to_string(), - VOUCHER_INFO.to_string(), - Hash::from_str(&state.tx_hash).map_err(|_| CredentialClientError::InvalidTxHash)?, - identity::PrivateKey::from_base58_string(&state.signing_keypair.private_key)?, - encryption::PrivateKey::from_base58_string(&state.encryption_keypair.private_key)?, - ); +pub(crate) async fn get_credential( + state: &State, + client: validator_client::Client, + shared_storage: PersistentStorage, +) -> Result<()> { + let epoch_id = client.nyxd.get_current_epoch().await?.epoch_id; + let threshold = client + .nyxd + .get_current_epoch_threshold() + .await? + .ok_or(CredentialClientError::NoThreshold)?; + let coconut_api_clients = CoconutApiClient::all_coconut_api_clients(&client, epoch_id).await?; let signature = obtain_aggregate_signature( - ¶ms, - &bandwidth_credential_attributes, + &state.params, + &state.voucher, &coconut_api_clients, + threshold, ) .await?; - println!("Signature: {:?}", signature.to_bs58()); + info!("Signature: {:?}", signature.to_bs58()); shared_storage .insert_coconut_credential( - state.amount.to_string(), + state.voucher.get_voucher_value(), VOUCHER_INFO.to_string(), - bandwidth_credential_attributes.get_private_attributes()[0].to_bs58(), - bandwidth_credential_attributes.get_private_attributes()[1].to_bs58(), + state.voucher.get_private_attributes()[0].to_bs58(), + state.voucher.get_private_attributes()[1].to_bs58(), signature.to_bs58(), + epoch_id.to_string(), ) .await?; Ok(()) } + +pub(crate) async fn recover_credentials( + client: validator_client::Client, + recovery_storage: &RecoveryStorage, + shared_storage: PersistentStorage, +) -> Result<()> { + for voucher in recovery_storage.unconsumed_vouchers()? { + let state = State { + voucher, + params: Parameters::new(TOTAL_ATTRIBUTES).unwrap(), + }; + if let Err(e) = get_credential(&state, client.clone(), shared_storage.clone()).await { + error!( + "Could not recover deposit {} due to {:?}, try again later", + state.voucher.tx_hash(), + e + ) + } else { + info!( + "Converted deposit {} to a credential, removing recovery data for it", + state.voucher.tx_hash() + ); + if let Err(e) = recovery_storage.remove_voucher(state.voucher.tx_hash().to_string()) { + warn!("Could not remove recovery data - {:?}", e); + } + } + } + + Ok(()) +} diff --git a/clients/credential/src/error.rs b/clients/credential/src/error.rs index 5cbc636e58..aa32d845c1 100644 --- a/clients/credential/src/error.rs +++ b/clients/credential/src/error.rs @@ -1,21 +1,25 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::time::SystemTimeError; use thiserror::Error; -use credential_storage::error::StorageError; -use credentials::error::Error as CredentialError; -use crypto::asymmetric::encryption::KeyRecoveryError; -use crypto::asymmetric::identity::Ed25519RecoveryError; -use validator_client::nymd::error::NymdError; +use nym_credential_storage::error::StorageError; +use nym_credentials::error::Error as CredentialError; +use nym_crypto::asymmetric::encryption::KeyRecoveryError; +use nym_crypto::asymmetric::identity::Ed25519RecoveryError; +use validator_client::nyxd::error::NyxdError; use validator_client::ValidatorClientError; pub type Result = std::result::Result; #[derive(Error, Debug)] pub enum CredentialClientError { - #[error("Nymd error: {0}")] - Nymd(#[from] NymdError), + #[error("IO error: {0}")] + IOError(#[from] std::io::Error), + + #[error("Nyxd error: {0}")] + Nyxd(#[from] NyxdError), #[error("Validator client error: {0}")] ValidatorClientError(#[from] ValidatorClientError), @@ -34,4 +38,10 @@ pub enum CredentialClientError { #[error("Could not use shared storage")] SharedStorageError(#[from] StorageError), + + #[error("Could not get system time")] + SysTimeError(#[from] SystemTimeError), + + #[error("Threshold not set yet")] + NoThreshold, } diff --git a/clients/credential/src/main.rs b/clients/credential/src/main.rs index f413673626..3965eba15f 100644 --- a/clients/credential/src/main.rs +++ b/clients/credential/src/main.rs @@ -1,56 +1,115 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -cfg_if::cfg_if! { - if #[cfg(feature = "coconut")] { +mod client; +mod commands; +mod error; +mod recovery_storage; +mod state; - mod client; - mod commands; - mod error; - mod state; +use commands::*; +use error::Result; +use log::*; +use nym_bin_common::completions::fig_generate; +use nym_config::{CRED_DB_FILE_NAME, DATA_DIR}; +use nym_network_defaults::{setup_env, NymNetworkDetails}; +use std::process::exit; +use std::time::{Duration, SystemTime}; - use error::Result; - use network_defaults::setup_env; - use completions::fig_generate; - use commands::*; - use config::{DATA_DIR, DB_FILE_NAME}; +use clap::{CommandFactory, Parser}; +use nym_bin_common::logging::setup_logging; +use validator_client::nyxd::traits::DkgQueryClient; +use validator_client::nyxd::CosmWasmClient; +use validator_client::Config; - use clap::{CommandFactory, Parser}; +const SAFETY_BUFFER_SECS: u64 = 60; // 1 minute - #[derive(Parser)] - #[clap(author = "Nymtech", version, about)] - struct Cli { - /// Path pointing to an env file that configures the client. - #[clap(short, long)] - pub(crate) config_env_file: Option, +#[derive(Parser)] +#[clap(author = "Nymtech", version, about)] +struct Cli { + /// Path pointing to an env file that configures the client. + #[clap(short, long)] + pub(crate) config_env_file: Option, - #[clap(subcommand)] - pub(crate) command: Command, - } + #[clap(subcommand)] + pub(crate) command: Command, +} - #[tokio::main] - async fn main() -> Result<()> { - let args = Cli::parse(); - setup_env(args.config_env_file.clone()); - let bin_name = "nym-credential-client"; - - match args.command { - Command::Run(r) => { - let db_path = r.client_home_directory.join(DATA_DIR).join(DB_FILE_NAME); - let shared_storage = credential_storage::initialise_storage(db_path).await; - - let state = deposit(&r.nymd_url, &r.mnemonic, r.amount).await?; - get_credential(&state, shared_storage).await?; - } - Command::Completions(c) => c.generate(&mut crate::Cli::into_app(), bin_name), - Command::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name) +async fn block_until_coconut_is_available( + client: &validator_client::Client, +) -> Result<()> { + loop { + let epoch = client.nyxd.get_current_epoch().await?; + let current_timestamp_secs = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_secs(); + if epoch.state.is_final() { + if current_timestamp_secs + SAFETY_BUFFER_SECS >= epoch.finish_timestamp.seconds() { + info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60); + exit(0); } - Ok(()) - } - } else { - fn main() { - println!("Crate only designed for coconut feature"); + break; + } else { + // Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries + let secs_until_final = epoch + .final_timestamp_secs() + .saturating_sub(current_timestamp_secs) + + 1; + info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final); + std::thread::sleep(Duration::from_secs(secs_until_final)); } } + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Cli::parse(); + setup_logging(); + setup_env(args.config_env_file.as_ref()); + let bin_name = "nym-credential-client"; + + match args.command { + Command::Run(r) => { + let db_path = r + .client_home_directory + .join(DATA_DIR) + .join(CRED_DB_FILE_NAME); + let shared_storage = nym_credential_storage::initialise_storage(db_path).await; + let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?; + + let network_details = NymNetworkDetails::new_from_env(); + let config = Config::try_from_nym_network_details(&network_details)?; + let client = validator_client::Client::new_query(config)?; + + block_until_coconut_is_available(&client).await?; + info!("Starting depositing funds, don't kill the process"); + + if !r.recovery_mode { + let state = deposit(&r.nyxd_url, &r.mnemonic, r.amount).await?; + if get_credential(&state, client, shared_storage) + .await + .is_err() + { + warn!("Failed to obtain credential. Dumping recovery data.",); + match recovery_storage.insert_voucher(&state.voucher) { + Ok(file_path) => { + warn!("Dumped recovery data to {:?}. Try using recovery mode to convert it to a credential", file_path); + } + Err(e) => { + error!("Could not dump recovery data to file system due to {:?}, the deposit will be lost!", e) + } + } + } + } else { + recover_credentials(client, &recovery_storage, shared_storage).await?; + } + } + Command::Completions(c) => c.generate(&mut crate::Cli::command(), bin_name), + Command::GenerateFigSpec => fig_generate(&mut crate::Cli::command(), bin_name), + } + + Ok(()) } diff --git a/clients/credential/src/recovery_storage.rs b/clients/credential/src/recovery_storage.rs new file mode 100644 index 0000000000..db81d2ff6a --- /dev/null +++ b/clients/credential/src/recovery_storage.rs @@ -0,0 +1,56 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use std::fs::{create_dir_all, read_dir, File}; +use std::io::{Read, Write}; +use std::path::PathBuf; + +pub struct RecoveryStorage { + recovery_dir: PathBuf, +} + +impl RecoveryStorage { + pub fn new(recovery_dir: PathBuf) -> std::io::Result { + create_dir_all(&recovery_dir)?; + Ok(Self { recovery_dir }) + } + + pub fn unconsumed_vouchers(&self) -> std::io::Result> { + Ok(read_dir(&self.recovery_dir)? + .filter_map(|entry| entry.ok()) + .filter_map(|entry| { + let path = entry.path(); + if path.is_file() { + Some(path) + } else { + None + } + }) + .filter_map(|path| File::open(path).ok()) + .filter_map(|mut f| { + let mut buff = Vec::new(); + if f.read_to_end(&mut buff).is_ok() { + Some(buff) + } else { + None + } + }) + .filter_map(|buff| BandwidthVoucher::try_from_bytes(&buff).ok())) + } + + pub fn insert_voucher(&self, voucher: &BandwidthVoucher) -> std::io::Result { + let file_name = voucher.tx_hash().to_string(); + let file_path = self.recovery_dir.join(file_name); + let mut file = File::create(&file_path)?; + let buff = voucher.to_bytes(); + file.write_all(&buff)?; + + Ok(file_path) + } + + pub fn remove_voucher(&self, file_name: String) -> std::io::Result<()> { + let file_path = self.recovery_dir.join(file_name); + std::fs::remove_file(file_path) + } +} diff --git a/clients/credential/src/state.rs b/clients/credential/src/state.rs index 98cbb307df..a4894a8ced 100644 --- a/clients/credential/src/state.rs +++ b/clients/credential/src/state.rs @@ -1,11 +1,11 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use serde::{Deserialize, Serialize}; +use nym_coconut_interface::Parameters; +use nym_credentials::coconut::bandwidth::BandwidthVoucher; -use crypto::asymmetric::{encryption, identity}; +use nym_crypto::asymmetric::{encryption, identity}; -#[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct KeyPair { pub public_key: String, pub private_key: String, @@ -29,10 +29,7 @@ impl From for KeyPair { } } -#[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct State { - pub amount: u64, - pub tx_hash: String, - pub signing_keypair: KeyPair, - pub encryption_keypair: KeyPair, + pub voucher: BandwidthVoucher, + pub params: Parameters, } diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index d97f7748fb..c7190f1b18 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.4" +version = "1.1.13" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" @@ -20,44 +20,36 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it # and the single instance of abortable we have should really be refactored anyway url = "2.2" -clap = { version = "3.2", features = ["cargo", "derive"] } +clap = { version = "4.0", features = ["cargo", "derive"] } dirs = "4.0" -log = "0.4" # self explanatory +lazy_static = "1.4.0" +log = { workspace = true } # self explanatory pretty_env_logger = "0.4" # for formatting log messages rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization serde_json = "1.0" thiserror = "1.0.34" tap = "1.0.1" -tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime +tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] } # async runtime tokio-tungstenite = "0.14" # websocket ## internal +nym-bin-common = { path = "../../common/bin-common" } client-core = { path = "../client-core", features = ["fs-surb-storage"] } -client-connections = { path = "../../common/client-connections" } -coconut-interface = { path = "../../common/coconut-interface", optional = true } -config = { path = "../../common/config" } -completions = { path = "../../common/completions" } -credential-storage = { path = "../../common/credential-storage" } -credentials = { path = "../../common/credentials", optional = true } -crypto = { path = "../../common/crypto" } -logging = { path = "../../common/logging"} +nym-coconut-interface = { path = "../../common/coconut-interface" } +nym-config = { path = "../../common/config" } +nym-credential-storage = { path = "../../common/credential-storage" } +nym-credentials = { path = "../../common/credentials" } +nym-crypto = { path = "../../common/crypto" } gateway-client = { path = "../../common/client-libs/gateway-client" } gateway-requests = { path = "../../gateway/gateway-requests" } -network-defaults = { path = "../../common/network-defaults" } -nymsphinx = { path = "../../common/nymsphinx" } -pemstore = { path = "../../common/pemstore" } -task = { path = "../../common/task" } -topology = { path = "../../common/topology" } -validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] } -version-checker = { path = "../../common/version-checker" } +nym-network-defaults = { path = "../../common/network-defaults" } +nym-sphinx = { path = "../../common/nymsphinx" } +nym-pemstore = { path = "../../common/pemstore" } +nym-task = { path = "../../common/task" } +nym-topology = { path = "../../common/topology" } +validator-client = { path = "../../common/client-libs/validator-client", features = ["nyxd-client"] } websocket-requests = { path = "websocket-requests" } -[features] -coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"] - [dev-dependencies] serde_json = "1.0" # for the "textsend" example - -[build-dependencies] -vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] } diff --git a/clients/native/build.rs b/clients/native/build.rs deleted file mode 100644 index 01b3a20dc6..0000000000 --- a/clients/native/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use vergen::{vergen, Config}; - -fn main() { - vergen(Config::default()).expect("failed to extract build metadata") -} diff --git a/clients/native/examples/js-examples/websocket/package-lock.json b/clients/native/examples/js-examples/websocket/package-lock.json index 4fc3fbbd83..0c20aed18c 100644 --- a/clients/native/examples/js-examples/websocket/package-lock.json +++ b/clients/native/examples/js-examples/websocket/package-lock.json @@ -477,13 +477,13 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" @@ -674,30 +674,42 @@ } }, "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "dev": true, "dependencies": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, "engines": { "node": ">= 0.8" } }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "node_modules/body-parser/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "engines": { "node": ">= 0.8" @@ -1006,23 +1018,17 @@ } }, "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, "dependencies": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" }, "engines": { "node": ">= 0.6" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "node_modules/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -1033,9 +1039,9 @@ } }, "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "dev": true, "engines": { "node": ">= 0.6" @@ -1180,10 +1186,14 @@ } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detect-node": { "version": "2.1.0", @@ -1299,7 +1309,7 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, "node_modules/electron-to-chromium": { @@ -1310,7 +1320,7 @@ "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true, "engines": { "node": ">= 0.8" @@ -1409,7 +1419,7 @@ "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "engines": { "node": ">= 0.6" @@ -1453,38 +1463,39 @@ } }, "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "dev": true, "dependencies": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -1499,11 +1510,23 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "node_modules/express/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -1617,23 +1640,32 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dev": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/follow-redirects": { "version": "1.14.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", @@ -1666,7 +1698,7 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, "engines": { "node": ">= 0.6" @@ -1928,26 +1960,38 @@ "dev": true }, "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "node_modules/http-errors/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } }, "node_modules/http-parser-js": { "version": "0.5.3", @@ -2379,7 +2423,7 @@ "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, "engines": { "node": ">= 0.6" @@ -2439,19 +2483,19 @@ } }, "node_modules/mime-db": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", - "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.31", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", - "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.48.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -2528,9 +2572,9 @@ "dev": true }, "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, "engines": { "node": ">= 0.6" @@ -2605,6 +2649,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", @@ -2637,9 +2690,9 @@ "dev": true }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "dependencies": { "ee-first": "1.1.1" @@ -2933,12 +2986,18 @@ } }, "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/queue-microtask": { @@ -2979,13 +3038,13 @@ } }, "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dev": true, "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -2994,9 +3053,9 @@ } }, "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "engines": { "node": ">= 0.8" @@ -3239,35 +3298,53 @@ } }, "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dev": true, "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -3322,24 +3399,24 @@ "dev": true }, "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dev": true, "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, "node_modules/shallow-clone": { @@ -3375,6 +3452,20 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -3642,9 +3733,9 @@ "dev": true }, "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "engines": { "node": ">=0.6" @@ -3671,7 +3762,7 @@ "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "engines": { "node": ">= 0.8" @@ -4793,13 +4884,13 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "acorn": { @@ -4939,27 +5030,35 @@ "dev": true }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "dev": true, "requires": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true } } @@ -5186,20 +5285,12 @@ "dev": true }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } + "safe-buffer": "5.2.1" } }, "content-type": { @@ -5209,9 +5300,9 @@ "dev": true }, "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "dev": true }, "cookie-signature": { @@ -5323,9 +5414,9 @@ "dev": true }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true }, "detect-node": { @@ -5421,7 +5512,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, "electron-to-chromium": { @@ -5432,7 +5523,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true }, "enhanced-resolve": { @@ -5503,7 +5594,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true }, "eventemitter3": { @@ -5535,38 +5626,39 @@ } }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "dev": true, "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -5578,10 +5670,16 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true } } @@ -5679,18 +5777,26 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" + }, + "dependencies": { + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } } }, "follow-redirects": { @@ -5708,7 +5814,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true }, "fs-monkey": { @@ -5900,22 +6006,28 @@ "dev": true }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true } } @@ -6230,7 +6342,7 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true }, "memfs": { @@ -6272,16 +6384,16 @@ "dev": true }, "mime-db": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", - "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.31", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", - "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.48.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -6343,9 +6455,9 @@ "dev": true }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true }, "neo-async": { @@ -6402,6 +6514,12 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, "object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", @@ -6425,9 +6543,9 @@ "dev": true }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "requires": { "ee-first": "1.1.1" @@ -6656,10 +6774,13 @@ } }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } }, "queue-microtask": { "version": "1.2.3", @@ -6682,21 +6803,21 @@ "dev": true }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dev": true, "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, "dependencies": { "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true } } @@ -6868,30 +6989,42 @@ } }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dev": true, "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true } } @@ -6946,21 +7079,21 @@ } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" } }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, "shallow-clone": { @@ -6987,6 +7120,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -7181,9 +7325,9 @@ "dev": true }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, "tslib": { @@ -7204,7 +7348,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true }, "uri-js": { diff --git a/clients/native/examples/websocket_binarysend.rs b/clients/native/examples/websocket_binarysend.rs index 2c22f0d03b..2b7fec32d0 100644 --- a/clients/native/examples/websocket_binarysend.rs +++ b/clients/native/examples/websocket_binarysend.rs @@ -1,5 +1,5 @@ use futures::{SinkExt, StreamExt}; -use nymsphinx::addressing::clients::Recipient; +use nym_sphinx::addressing::clients::Recipient; use tokio::net::TcpStream; use tokio_tungstenite::{ connect_async, tungstenite::protocol::Message, MaybeTlsStream, WebSocketStream, @@ -85,7 +85,7 @@ async fn send_file_without_reply() { let (mut ws_stream, _) = connect_async(uri).await.unwrap(); let recipient = get_self_address(&mut ws_stream).await; - println!("our full address is: {}", recipient); + println!("our full address is: {recipient}"); let read_data = std::fs::read("examples/dummy_file").unwrap(); diff --git a/clients/native/examples/websocket_textsend.rs b/clients/native/examples/websocket_textsend.rs index a6d00aa55f..9c673c3741 100644 --- a/clients/native/examples/websocket_textsend.rs +++ b/clients/native/examples/websocket_textsend.rs @@ -36,7 +36,7 @@ async fn send_text_with_reply() { let (mut ws_stream, _) = connect_async(uri).await.unwrap(); let recipient = get_self_address(&mut ws_stream).await; - println!("our full address is: {}", recipient); + println!("our full address is: {recipient}"); let send_request = json!({ "type" : "send", @@ -45,10 +45,7 @@ async fn send_text_with_reply() { "withReplySurb": true, }); - println!( - "sending {:?} (*with* reply SURB) over the mix network...", - message - ); + println!("sending {message:?} (*with* reply SURB) over the mix network..."); let response = send_message_and_get_json_response(&mut ws_stream, send_request.to_string()).await; @@ -59,10 +56,7 @@ async fn send_text_with_reply() { "replySurb": response["replySurb"] }); - println!( - "sending {:?} (using reply SURB!) over the mix network...", - reply_message - ); + println!("sending {reply_message:?} (using reply SURB!) over the mix network..."); let response = send_message_and_get_json_response(&mut ws_stream, reply_request.to_string()).await; @@ -76,7 +70,7 @@ async fn send_text_without_reply() { let (mut ws_stream, _) = connect_async(uri).await.unwrap(); let recipient = get_self_address(&mut ws_stream).await; - println!("our full address is: {}", recipient); + println!("our full address is: {recipient}"); let send_request = json!({ "type" : "send", @@ -85,10 +79,7 @@ async fn send_text_without_reply() { "withReplySurb": false, }); - println!( - "sending {:?} (*without* reply SURB) over the mix network...", - message - ); + println!("sending {message:?} (*without* reply SURB) over the mix network..."); let response = send_message_and_get_json_response(&mut ws_stream, send_request.to_string()).await; diff --git a/clients/native/src/client/config/mod.rs b/clients/native/src/client/config/mod.rs index 67902138ce..80795ca450 100644 --- a/clients/native/src/client/config/mod.rs +++ b/clients/native/src/client/config/mod.rs @@ -2,12 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::config::template::config_template; -pub use client_core::config::MISSING_VALUE; -use client_core::config::{ClientCoreConfigTrait, Config as BaseConfig, DebugConfig}; -use config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT; -use config::NymConfig; +use client_core::config::ClientCoreConfigTrait; +use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT; +use nym_config::{NymConfig, OptionalSet}; use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::net::{IpAddr, Ipv4Addr}; use std::path::PathBuf; +use std::str::FromStr; + +pub use client_core::config::Config as BaseConfig; +pub use client_core::config::MISSING_VALUE; +pub use client_core::config::{DebugConfig, GatewayEndpointConfig}; mod template; @@ -92,6 +98,20 @@ impl Config { self } + pub fn with_disabled_socket(mut self, disabled: bool) -> Self { + if disabled { + self.socket.socket_type = SocketType::None; + } else { + self.socket.socket_type = SocketType::WebSocket; + } + self + } + + pub fn with_host(mut self, host: IpAddr) -> Self { + self.socket.host = host; + self + } + pub fn with_port(mut self, port: u16) -> Self { self.socket.listening_port = port; self @@ -118,15 +138,64 @@ impl Config { self.socket.socket_type } + pub fn get_listening_ip(&self) -> IpAddr { + self.socket.host + } + pub fn get_listening_port(&self) -> u16 { self.socket.listening_port } + + // poor man's 'builder' method + pub fn with_base(mut self, f: F, val: T) -> Self + where + F: Fn(BaseConfig, T) -> BaseConfig, + { + self.base = f(self.base, val); + self + } + + // helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config + // (plz, lets refactor it) + pub fn with_optional_ext(mut self, f: F, val: Option) -> Self + where + F: Fn(BaseConfig, T) -> BaseConfig, + { + self.base = self.base.with_optional(f, val); + self + } + + pub fn with_optional_env_ext(mut self, f: F, val: Option, env_var: &str) -> Self + where + F: Fn(BaseConfig, T) -> BaseConfig, + T: FromStr, + ::Err: Debug, + { + self.base = self.base.with_optional_env(f, val, env_var); + self + } + + pub fn with_optional_custom_env_ext( + mut self, + f: F, + val: Option, + env_var: &str, + parser: G, + ) -> Self + where + F: Fn(BaseConfig, T) -> BaseConfig, + G: Fn(&str) -> T, + { + self.base = self.base.with_optional_custom_env(f, val, env_var, parser); + self + } } #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(deny_unknown_fields)] +#[serde(default, deny_unknown_fields)] pub struct Socket { socket_type: SocketType, + host: IpAddr, listening_port: u16, } @@ -134,6 +203,7 @@ impl Default for Socket { fn default() -> Self { Socket { socket_type: SocketType::WebSocket, + host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT, } } diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs index 31ca091282..df32829b22 100644 --- a/clients/native/src/client/config/template.rs +++ b/clients/native/src/client/config/template.rs @@ -23,16 +23,16 @@ id = '{{ client.id }}' # to claim bandwidth without presenting bandwidth credentials. disabled_credentials_mode = {{ client.disabled_credentials_mode }} -# Addresses to nymd validators via which the client can communicate with the chain. -validator_urls = [ - {{#each client.validator_urls }} +# Addresses to nyxd validators via which the client can communicate with the chain. +nyxd_urls = [ + {{#each client.nyxd_urls }} '{{this}}', {{/each}} ] # Addresses to APIs running on validator from which the client gets the view of the network. -validator_api_urls = [ - {{#each client.validator_api_urls }} +nym_api_urls = [ + {{#each client.nym_api_urls }} '{{this}}', {{/each}} ] @@ -93,6 +93,9 @@ socket_type = '{{ socket.socket_type }}' # will be listening for incoming requests listening_port = {{ socket.listening_port }} +# if applicable (for the case of 'WebSocket'), the ip address on which the client +# will be listening for incoming requests +host = '{{ socket.host }}' ##### logging configuration options ##### diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index cabe049c45..aedfc0494d 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -1,26 +1,32 @@ // Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::error::Error; + use crate::client::config::Config; use crate::error::ClientError; use crate::websocket; -use client_connections::TransmissionLane; use client_core::client::base_client::{ - non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, + non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState, }; use client_core::client::inbound_messages::InputMessage; -use client_core::client::key_manager::KeyManager; -use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver}; +use client_core::client::received_buffer::{ + ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver, +}; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use futures::channel::mpsc; use gateway_client::bandwidth::BandwidthController; use log::*; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; -use nymsphinx::receiver::ReconstructedMessage; -use task::{wait_for_signal, ShutdownNotifier}; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_task::connections::TransmissionLane; +use nym_task::TaskManager; +use tokio::sync::watch::error::SendError; +use validator_client::nyxd::QueryNyxdClient; -pub(crate) mod config; +pub use client_core::client::key_manager::KeyManager; +pub use nym_sphinx::addressing::clients::Recipient; +pub use nym_sphinx::receiver::ReconstructedMessage; +pub mod config; pub struct SocketClient { /// Client configuration options, including, among other things, packet sending rates, @@ -42,104 +48,102 @@ impl SocketClient { } } - async fn create_bandwidth_controller(config: &Config) -> BandwidthController { - #[cfg(feature = "coconut")] - let bandwidth_controller = { - let details = network_defaults::NymNetworkDetails::new_from_env(); - let mut client_config = - validator_client::Config::try_from_nym_network_details(&details) - .expect("failed to construct validator client config"); - let nymd_url = config - .get_base() - .get_validator_endpoints() - .pop() - .expect("No nymd validator endpoint provided"); - let api_url = config - .get_base() - .get_validator_api_endpoints() - .pop() - .expect("No validator api endpoint provided"); - // overwrite env configuration with config URLs - client_config = client_config.with_urls(nymd_url, api_url); - let client = validator_client::Client::new_query(client_config) - .expect("Could not construct query client"); - let coconut_api_clients = - validator_client::CoconutApiClient::all_coconut_api_clients(&client) - .await - .expect("Could not query api clients"); - BandwidthController::new( - credential_storage::initialise_storage(config.get_base().get_database_path()).await, - coconut_api_clients, - ) - }; - #[cfg(not(feature = "coconut"))] - let bandwidth_controller = BandwidthController::new( - credential_storage::initialise_storage(config.get_base().get_database_path()).await, + pub fn new_with_keys(config: Config, key_manager: KeyManager) -> Self { + SocketClient { + config, + key_manager, + } + } + + async fn create_bandwidth_controller(config: &Config) -> BandwidthController { + let details = nym_network_defaults::NymNetworkDetails::new_from_env(); + let mut client_config = validator_client::Config::try_from_nym_network_details(&details) + .expect("failed to construct validator client config"); + let nyxd_url = config + .get_base() + .get_validator_endpoints() + .pop() + .expect("No nyxd validator endpoint provided"); + let api_url = config + .get_base() + .get_nym_api_endpoints() + .pop() + .expect("No validator api endpoint provided"); + // overwrite env configuration with config URLs + client_config = client_config.with_urls(nyxd_url, api_url); + let client = validator_client::Client::new_query(client_config) + .expect("Could not construct query client"); + BandwidthController::new( + nym_credential_storage::initialise_storage(config.get_base().get_database_path()).await, + client, ) - .expect("Could not create bandwidth controller"); - bandwidth_controller } fn start_websocket_listener( config: &Config, client_input: ClientInput, client_output: ClientOutput, - self_address: Recipient, + client_state: ClientState, + self_address: &Recipient, + shutdown: nym_task::TaskClient, ) { info!("Starting websocket listener..."); let ClientInput { - shared_lane_queue_lengths, connection_command_sender, input_sender, } = client_input; - let received_buffer_request_sender = client_output.received_buffer_request_sender; + let ClientOutput { + received_buffer_request_sender, + } = client_output; - let websocket_handler = websocket::Handler::new( + let ClientState { + shared_lane_queue_lengths, + reply_controller_sender, + .. + } = client_state; + + let websocket_handler = websocket::HandlerBuilder::new( input_sender, connection_command_sender, received_buffer_request_sender, self_address, shared_lane_queue_lengths, + reply_controller_sender, ); - websocket::Listener::new(config.get_listening_port()).start(websocket_handler); + websocket::Listener::new(config.get_listening_ip(), config.get_listening_port()) + .start(websocket_handler, shutdown); } /// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent) - pub async fn run_socket_forever(self) -> Result<(), ClientError> { - let mut shutdown = self.start_socket().await?; - wait_for_signal().await; - - println!( - "Received signal - the client will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)." - ); - - log::info!("Sending shutdown"); - shutdown.signal_shutdown().ok(); - - // Some of these components have shutdown signalling implemented as part of socks5 work, - // but since it's not fully implemented (yet) for all the components of the native client, - // we don't try to wait and instead just stop immediately. - log::info!("Waiting for tasks to finish... (Press ctrl-c to force)"); - shutdown.wait_for_shutdown().await; + pub async fn run_socket_forever(self) -> Result<(), Box> { + let shutdown = self.start_socket().await?; + let res = shutdown.catch_interrupt().await; log::info!("Stopping nym-client"); - Ok(()) + res } - pub async fn start_socket(self) -> Result { + pub async fn start_socket(self) -> Result { if !self.config.get_socket_type().is_websocket() { return Err(ClientError::InvalidSocketMode); } + // don't create bandwidth controller if credentials are disabled + let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() { + None + } else { + Some(Self::create_bandwidth_controller(&self.config).await) + }; + let base_builder = BaseClientBuilder::new_from_base_config( self.config.get_base(), self.key_manager, - Some(Self::create_bandwidth_controller(&self.config).await), + bandwidth_controller, non_wasm_helpers::setup_fs_reply_surb_backend( - self.config.get_base().get_reply_surb_database_path(), + Some(self.config.get_base().get_reply_surb_database_path()), self.config.get_debug_settings(), ) .await?, @@ -149,13 +153,21 @@ impl SocketClient { let mut started_client = base_builder.start_base().await?; let client_input = started_client.client_input.register_producer(); let client_output = started_client.client_output.register_consumer(); + let client_state = started_client.client_state; - Self::start_websocket_listener(&self.config, client_input, client_output, self_address); + Self::start_websocket_listener( + &self.config, + client_input, + client_output, + client_state, + &self_address, + started_client.task_manager.subscribe(), + ); info!("Client startup finished!"); info!("The address of this client is: {}", self_address); - Ok(started_client.shutdown_notifier) + Ok(started_client.task_manager) } pub async fn start_direct(self) -> Result { @@ -163,17 +175,26 @@ impl SocketClient { return Err(ClientError::InvalidSocketMode); } + // don't create bandwidth controller if credentials are disabled + let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() { + None + } else { + Some(Self::create_bandwidth_controller(&self.config).await) + }; + let base_client = BaseClientBuilder::new_from_base_config( self.config.get_base(), self.key_manager, - Some(Self::create_bandwidth_controller(&self.config).await), + bandwidth_controller, non_wasm_helpers::setup_fs_reply_surb_backend( - self.config.get_base().get_reply_surb_database_path(), + Some(self.config.get_base().get_reply_surb_database_path()), self.config.get_debug_settings(), ) .await?, ); + let address = base_client.as_mix_recipient(); + let mut started_client = base_client.start_base().await?; let client_input = started_client.client_input.register_producer(); let client_output = started_client.client_output.register_consumer(); @@ -191,21 +212,38 @@ impl SocketClient { Ok(DirectClient { client_input, + _received_buffer_request_sender: client_output.received_buffer_request_sender, reconstructed_receiver, - _shutdown_notifier: started_client.shutdown_notifier, + address, + shutdown_notifier: started_client.task_manager, }) } } pub struct DirectClient { client_input: ClientInput, + // make sure to not drop the channel + _received_buffer_request_sender: ReceivedBufferRequestSender, reconstructed_receiver: ReconstructedMessagesReceiver, + address: Recipient, // we need to keep reference to this guy otherwise things will start dropping - _shutdown_notifier: ShutdownNotifier, + shutdown_notifier: TaskManager, } impl DirectClient { + pub fn address(&self) -> &Recipient { + &self.address + } + + pub fn signal_shutdown(&self) -> Result<(), SendError<()>> { + self.shutdown_notifier.signal_shutdown() + } + + pub async fn wait_for_shutdown(&mut self) { + self.shutdown_notifier.wait_for_shutdown().await + } + /// EXPERIMENTAL DIRECT RUST API /// It's untested and there are absolutely no guarantees about it (but seems to have worked /// well enough in local tests) diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index 3caf3a5d0d..c41c4796a0 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -7,10 +7,12 @@ use crate::{ error::ClientError, }; use clap::Args; -use config::NymConfig; -use nymsphinx::addressing::clients::Recipient; +use nym_config::NymConfig; +use nym_crypto::asymmetric::identity; +use nym_sphinx::addressing::clients::Recipient; use serde::Serialize; use std::fmt::Display; +use std::net::IpAddr; use tap::TapFallible; #[derive(Args, Clone)] @@ -21,43 +23,52 @@ pub(crate) struct Init { /// Id of the gateway we are going to connect to. #[clap(long)] - gateway: Option, + gateway: Option, + + /// Specifies whether the new gateway should be determined based by latency as opposed to being chosen + /// uniformly. + #[clap(long, conflicts_with = "gateway")] + latency_based_selection: bool, /// Force register gateway. WARNING: this will overwrite any existing keys for the given id, /// potentially causing loss of access. #[clap(long)] force_register_gateway: bool, - /// Comma separated list of rest endpoints of the nymd validators - #[clap(long)] - nymd_validators: Option, + /// Comma separated list of rest endpoints of the nyxd validators + #[clap(long, alias = "nyxd_validators", value_delimiter = ',', hide = true)] + nyxd_urls: Option>, /// Comma separated list of rest endpoints of the API validators - #[clap(long)] - api_validators: Option, + #[clap(long, alias = "api_validators", value_delimiter = ',')] + // the alias here is included for backwards compatibility (1.1.4 and before) + nym_apis: Option>, /// Whether to not start the websocket #[clap(long)] - disable_socket: bool, + disable_socket: Option, /// Port for the socket (if applicable) to listen on in all subsequent runs #[clap(short, long)] port: Option, + /// Ip for the socket (if applicable) to listen for requests. + #[clap(long)] + host: Option, + /// Mostly debug-related option to increase default traffic rate so that you would not need to /// modify config post init - #[clap(long, hidden = true)] + #[clap(long, hide = true)] fastmode: bool, /// Disable loop cover traffic and the Poisson rate limiter (for debugging only) - #[clap(long, hidden = true)] + #[clap(long, hide = true)] no_cover: bool, /// Set this client to work in a enabled credentials mode that would attempt to use gateway /// with bandwidth credential requirement. - #[cfg(feature = "coconut")] - #[clap(long)] - enabled_credentials_mode: bool, + #[clap(long, hide = true)] + enabled_credentials_mode: Option, /// Save a summary of the initialization to a json file #[clap(long)] @@ -67,14 +78,14 @@ pub(crate) struct Init { impl From for OverrideConfig { fn from(init_config: Init) -> Self { OverrideConfig { - nymd_validators: init_config.nymd_validators, - api_validators: init_config.api_validators, + nym_apis: init_config.nym_apis, disable_socket: init_config.disable_socket, port: init_config.port, + host: init_config.host, fastmode: init_config.fastmode, no_cover: init_config.no_cover, - #[cfg(feature = "coconut")] + nyxd_urls: init_config.nyxd_urls, enabled_credentials_mode: init_config.enabled_credentials_mode, } } @@ -108,18 +119,17 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> { let id = &args.id; - let already_init = Config::default_config_file_path(Some(id)).exists(); + let already_init = Config::default_config_file_path(id).exists(); if already_init { - println!( - "Client \"{}\" was already initialised before! \ - Config information will be overwritten (but keys will be kept)!", - id - ); + println!("Client \"{id}\" was already initialised before"); } // Usually you only register with the gateway on the first init, however you can force // re-registering if wanted. let user_wants_force_register = args.force_register_gateway; + if user_wants_force_register { + println!("Instructed to force registering gateway. This might overwrite keys!"); + } // If the client was already initialized, don't generate new keys and don't re-register with // the gateway (because this would create a new shared key). @@ -127,22 +137,23 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> { let register_gateway = !already_init || user_wants_force_register; // Attempt to use a user-provided gateway, if possible - let user_chosen_gateway_id = args.gateway.clone(); + let user_chosen_gateway_id = args.gateway; // Load and potentially override config let mut config = override_config(Config::new(id), OverrideConfig::from(args.clone())); // Setup gateway by either registering a new one, or creating a new config from the selected // one but with keys kept, or reusing the gateway configuration. - let gateway = client_core::init::setup_gateway::<_, Config, _>( + let gateway = client_core::init::setup_gateway_from_config::( register_gateway, user_chosen_gateway_id, config.get_base(), + args.latency_based_selection, ) .await .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; - config.get_base_mut().with_gateway_endpoint(gateway); + config.get_base_mut().set_gateway_endpoint(gateway); config.save_to_file(None).tap_err(|_| { log::error!("Failed to save the config file"); @@ -152,20 +163,20 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> { let address = client_core::init::get_client_address_from_stored_keys(config.get_base())?; let init_results = InitResults::new(&config, &address); - println!("{}", init_results); + println!("{init_results}"); // Output summary to a json file, if specified if args.output_json { client_core::init::output_to_json(&init_results, "client_init_results.json"); } - println!("\nThe address of this client is: {}\n", address); + println!("\nThe address of this client is: {address}\n"); Ok(()) } fn print_saved_config(config: &Config) { let config_save_location = config.get_config_file_save_location(); - println!("Saved configuration file to {:?}", config_save_location); + println!("Saved configuration file to {config_save_location:?}"); println!("Using gateway: {}", config.get_base().get_gateway_id()); log::debug!("Gateway id: {}", config.get_base().get_gateway_id()); log::debug!("Gateway owner: {}", config.get_base().get_gateway_owner()); diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index 9dc6a2ac33..b8c05c4cc7 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -1,53 +1,32 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::client::config::{Config, SocketType}; -use crate::error::ClientError; +use crate::client::config::{BaseConfig, Config}; use clap::CommandFactory; use clap::{Parser, Subcommand}; -use completions::{fig_generate, ArgShell}; +use lazy_static::lazy_static; +use nym_bin_common::build_information::BinaryBuildInformation; +use nym_bin_common::completions::{fig_generate, ArgShell}; +use nym_config::OptionalSet; +use std::error::Error; +use std::net::IpAddr; pub(crate) mod init; pub(crate) mod run; pub(crate) mod upgrade; -fn long_version() -> String { - format!( - r#" -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -"#, - "Build Timestamp:", - env!("VERGEN_BUILD_TIMESTAMP"), - "Build Version:", - env!("VERGEN_BUILD_SEMVER"), - "Commit SHA:", - env!("VERGEN_GIT_SHA"), - "Commit Date:", - env!("VERGEN_GIT_COMMIT_TIMESTAMP"), - "Commit Branch:", - env!("VERGEN_GIT_BRANCH"), - "rustc Version:", - env!("VERGEN_RUSTC_SEMVER"), - "rustc Channel:", - env!("VERGEN_RUSTC_CHANNEL"), - "cargo Profile:", - env!("VERGEN_CARGO_PROFILE"), - ) +lazy_static! { + pub static ref PRETTY_BUILD_INFORMATION: String = + BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print(); } -fn long_version_static() -> &'static str { - Box::leak(long_version().into_boxed_str()) +// Helper for passing LONG_VERSION to clap +fn pretty_build_info_static() -> &'static str { + &PRETTY_BUILD_INFORMATION } #[derive(Parser)] -#[clap(author = "Nymtech", version, long_version = long_version_static(), about)] +#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] pub(crate) struct Cli { /// Path pointing to an env file that configures the client. #[clap(short, long)] @@ -75,78 +54,52 @@ pub(crate) enum Commands { // Configuration that can be overridden. pub(crate) struct OverrideConfig { - nymd_validators: Option, - api_validators: Option, - disable_socket: bool, + nym_apis: Option>, + disable_socket: Option, port: Option, + host: Option, fastmode: bool, no_cover: bool, - - #[cfg(feature = "coconut")] - enabled_credentials_mode: bool, + nyxd_urls: Option>, + enabled_credentials_mode: Option, } -pub(crate) async fn execute(args: &Cli) -> Result<(), ClientError> { +pub(crate) async fn execute(args: &Cli) -> Result<(), Box> { let bin_name = "nym-native-client"; match &args.command { Commands::Init(m) => init::execute(m).await?, Commands::Run(m) => run::execute(m).await?, Commands::Upgrade(m) => upgrade::execute(m), - Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name), - Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name), + Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name), + Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), } Ok(()) } -pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config { - if let Some(raw_validators) = args.nymd_validators { - config - .get_base_mut() - .set_custom_validators(config::parse_validators(&raw_validators)); - } else if std::env::var(network_defaults::var_names::CONFIGURED).is_ok() { - let raw_validators = std::env::var(network_defaults::var_names::NYMD_VALIDATOR) - .expect("nymd validator not set"); - config - .get_base_mut() - .set_custom_validators(config::parse_validators(&raw_validators)); - } - if let Some(raw_validators) = args.api_validators { - config - .get_base_mut() - .set_custom_validator_apis(config::parse_validators(&raw_validators)); - } else if std::env::var(network_defaults::var_names::CONFIGURED).is_ok() { - let raw_validators = std::env::var(network_defaults::var_names::API_VALIDATOR) - .expect("api validator not set"); - config - .get_base_mut() - .set_custom_validator_apis(config::parse_validators(&raw_validators)); - } - - if args.disable_socket { - config = config.with_socket(SocketType::None); - } - - if let Some(port) = args.port { - config = config.with_port(port); - } - - #[cfg(feature = "coconut")] - { - if args.enabled_credentials_mode { - config.get_base_mut().with_disabled_credentials(false) - } - } - - if args.fastmode { - config.get_base_mut().set_high_default_traffic_volume(); - } - - if args.no_cover { - config.get_base_mut().set_no_cover_traffic(); - } - +pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { config + .with_optional(Config::with_disabled_socket, args.disable_socket) + .with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode) + .with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover) + .with_optional(Config::with_port, args.port) + .with_optional(Config::with_host, args.host) + .with_optional_custom_env_ext( + BaseConfig::with_custom_nym_apis, + args.nym_apis, + nym_network_defaults::var_names::NYM_API, + nym_config::parse_urls, + ) + .with_optional_custom_env_ext( + BaseConfig::with_custom_nyxd, + args.nyxd_urls, + nym_network_defaults::var_names::NYXD, + nym_config::parse_urls, + ) + .with_optional_ext( + BaseConfig::with_disabled_credentials, + args.enabled_credentials_mode.map(|b| !b), + ) } #[cfg(test)] diff --git a/clients/native/src/commands/run.rs b/clients/native/src/commands/run.rs index 5eb6939944..62ca82e4dd 100644 --- a/clients/native/src/commands/run.rs +++ b/clients/native/src/commands/run.rs @@ -1,6 +1,9 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::error::Error; +use std::net::IpAddr; + use crate::{ client::{config::Config, SocketClient}, commands::{override_config, OverrideConfig}, @@ -8,9 +11,10 @@ use crate::{ }; use clap::Args; -use config::NymConfig; use log::*; -use version_checker::is_minor_version_compatible; +use nym_bin_common::version_checker::is_minor_version_compatible; +use nym_config::NymConfig; +use nym_crypto::asymmetric::identity; #[derive(Args, Clone)] pub(crate) struct Run { @@ -18,53 +22,57 @@ pub(crate) struct Run { #[clap(long)] id: String, - /// Comma separated list of rest endpoints of the nymd validators - #[clap(long)] - nymd_validators: Option, + /// Comma separated list of rest endpoints of the nyxd validators + #[clap(long, alias = "nyxd_validators", value_delimiter = ',', hide = true)] + nyxd_urls: Option>, /// Comma separated list of rest endpoints of the API validators - #[clap(long)] - api_validators: Option, + #[clap(long, alias = "api_validators", value_delimiter = ',')] + // the alias here is included for backwards compatibility (1.1.4 and before) + nym_apis: Option>, /// Id of the gateway we want to connect to. If overridden, it is user's responsibility to /// ensure prior registration happened #[clap(long)] - gateway: Option, + gateway: Option, /// Whether to not start the websocket #[clap(long)] - disable_socket: bool, + disable_socket: Option, /// Port for the socket to listen on #[clap(short, long)] port: Option, + /// Ip for the socket (if applicable) to listen for requests. + #[clap(long)] + host: Option, + /// Mostly debug-related option to increase default traffic rate so that you would not need to /// modify config post init - #[clap(long, hidden = true)] + #[clap(long, hide = true)] fastmode: bool, /// Disable loop cover traffic and the Poisson rate limiter (for debugging only) - #[clap(long, hidden = true)] + #[clap(long, hide = true)] no_cover: bool, /// Set this client to work in a enabled credentials mode that would attempt to use gateway /// with bandwidth credential requirement. - #[cfg(feature = "coconut")] - #[clap(long)] - enabled_credentials_mode: bool, + #[clap(long, hide = true)] + enabled_credentials_mode: Option, } impl From for OverrideConfig { fn from(run_config: Run) -> Self { OverrideConfig { - nymd_validators: run_config.nymd_validators, - api_validators: run_config.api_validators, + nym_apis: run_config.nym_apis, disable_socket: run_config.disable_socket, port: run_config.port, + host: run_config.host, fastmode: run_config.fastmode, no_cover: run_config.no_cover, - #[cfg(feature = "coconut")] + nyxd_urls: run_config.nyxd_urls, enabled_credentials_mode: run_config.enabled_credentials_mode, } } @@ -89,14 +97,14 @@ fn version_check(cfg: &Config) -> bool { } } -pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> { +pub(crate) async fn execute(args: &Run) -> Result<(), Box> { let id = &args.id; - let mut config = match Config::load_from_file(Some(id)) { + let mut config = match Config::load_from_file(id) { Ok(cfg) => cfg, Err(err) => { - error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err); - return Err(ClientError::FailedToLoadConfig(id.to_string())); + error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id); + return Err(Box::new(ClientError::FailedToLoadConfig(id.to_string()))); } }; @@ -109,7 +117,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> { if !version_check(&config) { error!("failed the local version check"); - return Err(ClientError::FailedLocalVersionCheck); + return Err(Box::new(ClientError::FailedLocalVersionCheck)); } SocketClient::new(config).run_socket_forever().await diff --git a/clients/native/src/commands/upgrade.rs b/clients/native/src/commands/upgrade.rs index c84073200c..97e7dddba6 100644 --- a/clients/native/src/commands/upgrade.rs +++ b/clients/native/src/commands/upgrade.rs @@ -3,8 +3,8 @@ use crate::client::config::{Config, MISSING_VALUE}; -use config::NymConfig; -use version_checker::Version; +use nym_bin_common::version_checker::Version; +use nym_config::NymConfig; use clap::Args; use std::fmt::Display; @@ -17,36 +17,26 @@ fn fail_upgrade(from_version: D1, to_version: D2) -> ! } fn print_start_upgrade(from: D1, to: D2) { - println!( - "\n==================\nTrying to upgrade client from {} to {} ...", - from, to - ); + println!("\n==================\nTrying to upgrade client from {from} to {to} ..."); } fn print_failed_upgrade(from: D1, to: D2) { - eprintln!( - "Upgrade from {} to {} failed!\n==================\n", - from, to - ); + eprintln!("Upgrade from {from} to {to} failed!\n==================\n"); } fn print_successful_upgrade(from: D1, to: D2) { - println!( - "Upgrade from {} to {} was successful!\n==================\n", - from, to - ); + println!("Upgrade from {from} to {to} was successful!\n==================\n"); } fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! { eprintln!( - "Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!", - config_version, package_version + "Cannot perform upgrade from {config_version} to {package_version}. Your version is too old to perform the upgrade.!" ); process::exit(1) } fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! { - eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, current_version); + eprintln!("Cannot perform upgrade from {config_version} to {current_version}. Please let the developers know about this issue if you expected it to work!"); process::exit(1) } @@ -59,14 +49,13 @@ pub(crate) struct Upgrade { fn parse_config_version(config: &Config) -> Version { let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| { - eprintln!("failed to parse client version! - {:?}", err); + eprintln!("failed to parse client version! - {err}"); process::exit(1) }); if version.is_prerelease() || !version.build.is_empty() { eprintln!( - "Trying to upgrade from a non-released version {}. This is not supported!", - version + "Trying to upgrade from a non-released version {version}. This is not supported!" ); process::exit(1) } @@ -81,10 +70,7 @@ fn parse_package_version() -> Version { // however, we are not using them ourselves at the moment and hence it should be fine. // if we change our mind, we could easily tweak this code if version.is_prerelease() || !version.build.is_empty() { - eprintln!( - "Trying to upgrade to a non-released version {}. This is not supported!", - version - ); + eprintln!("Trying to upgrade to a non-released version {version}. This is not supported!"); process::exit(1) } @@ -110,7 +96,7 @@ fn minor_0_12_upgrade( .set_custom_version(to_version.to_string().as_ref()); config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); + eprintln!("failed to overwrite config file! - {err}"); print_failed_upgrade(config_version, &to_version); process::exit(1); }); @@ -145,8 +131,8 @@ pub(crate) fn execute(args: &Upgrade) { let id = &args.id; - let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { - eprintln!("failed to load existing config file! - {:?}", err); + let existing_config = Config::load_from_file(id).unwrap_or_else(|err| { + eprintln!("failed to load existing config file! - {err}"); process::exit(1) }); diff --git a/clients/native/src/error.rs b/clients/native/src/error.rs index 050e79ebc5..700e6e9336 100644 --- a/clients/native/src/error.rs +++ b/clients/native/src/error.rs @@ -1,4 +1,3 @@ -use client_core::client::replies::reply_storage::fs_backend; use client_core::error::ClientCoreError; #[derive(thiserror::Error, Debug)] @@ -7,7 +6,7 @@ pub enum ClientError { IoError(#[from] std::io::Error), #[error("client-core error: {0}")] - ClientCoreError(#[from] ClientCoreError), + ClientCoreError(#[from] ClientCoreError), #[error("Failed to load config for: {0}")] FailedToLoadConfig(String), diff --git a/clients/native/src/main.rs b/clients/native/src/main.rs index 00529494e7..48965e7c1a 100644 --- a/clients/native/src/main.rs +++ b/clients/native/src/main.rs @@ -1,10 +1,11 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use clap::{crate_version, Parser}; -use error::ClientError; -use logging::setup_logging; -use network_defaults::setup_env; +use std::error::Error; + +use clap::{crate_name, crate_version, Parser}; +use nym_bin_common::logging::{banner, setup_logging}; +use nym_network_defaults::setup_env; pub mod client; pub mod commands; @@ -12,28 +13,11 @@ pub mod error; pub mod websocket; #[tokio::main] -async fn main() -> Result<(), ClientError> { +async fn main() -> Result<(), Box> { setup_logging(); - println!("{}", banner()); + println!("{}", banner(crate_name!(), crate_version!())); let args = commands::Cli::parse(); - setup_env(args.config_env_file.clone()); + setup_env(args.config_env_file.as_ref()); commands::execute(&args).await } - -fn banner() -> String { - format!( - r#" - - _ __ _ _ _ __ ___ - | '_ \| | | | '_ \ _ \ - | | | | |_| | | | | | | - |_| |_|\__, |_| |_| |_| - |___/ - - (client - version {:}) - - "#, - crate_version!() - ) -} diff --git a/clients/native/src/websocket/handler.rs b/clients/native/src/websocket/handler.rs index 7d8d16e97f..e0d3501927 100644 --- a/clients/native/src/websocket/handler.rs +++ b/clients/native/src/websocket/handler.rs @@ -1,9 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use client_connections::{ - ConnectionCommand, ConnectionCommandSender, LaneQueueLengths, TransmissionLane, -}; +use client_core::client::replies::reply_controller::requests::ReplyControllerSender; use client_core::client::{ inbound_messages::{InputMessage, InputMessageSender}, received_buffer::{ @@ -13,10 +11,15 @@ use client_core::client::{ use futures::channel::mpsc; use futures::{SinkExt, StreamExt}; use log::*; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; -use nymsphinx::receiver::ReconstructedMessage; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::receiver::ReconstructedMessage; +use nym_task::connections::{ + ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane, +}; +use std::time::Duration; use tokio::net::TcpStream; +use tokio::time::Instant; use tokio_tungstenite::{ accept_async, tungstenite::{protocol::Message as WsMessage, Error as WsError}, @@ -24,14 +27,53 @@ use tokio_tungstenite::{ }; use websocket_requests::{requests::ClientRequest, responses::ServerResponse}; +#[derive(Default)] enum ReceivedResponseType { + #[default] Binary, Text, } -impl Default for ReceivedResponseType { - fn default() -> Self { - ReceivedResponseType::Binary +pub(crate) struct HandlerBuilder { + msg_input: InputMessageSender, + client_connection_tx: ConnectionCommandSender, + buffer_requester: ReceivedBufferRequestSender, + self_full_address: Recipient, + lane_queue_lengths: LaneQueueLengths, + reply_controller_sender: ReplyControllerSender, +} + +impl HandlerBuilder { + pub(crate) fn new( + msg_input: InputMessageSender, + client_connection_tx: ConnectionCommandSender, + buffer_requester: ReceivedBufferRequestSender, + self_full_address: &Recipient, + lane_queue_lengths: LaneQueueLengths, + reply_controller_sender: ReplyControllerSender, + ) -> Self { + Self { + msg_input, + client_connection_tx, + buffer_requester, + self_full_address: *self_full_address, + lane_queue_lengths, + reply_controller_sender, + } + } + + // TODO: make sure we only ever have one active handler + pub fn create_active_handler(&self) -> Handler { + Handler { + msg_input: self.msg_input.clone(), + client_connection_tx: self.client_connection_tx.clone(), + buffer_requester: self.buffer_requester.clone(), + self_full_address: self.self_full_address, + socket: None, + received_response_type: Default::default(), + lane_queue_lengths: self.lane_queue_lengths.clone(), + reply_controller_sender: self.reply_controller_sender.clone(), + } } } @@ -43,21 +85,7 @@ pub(crate) struct Handler { socket: Option>, received_response_type: ReceivedResponseType, lane_queue_lengths: LaneQueueLengths, -} - -// clone is used to use handler on a new connection, which initially is `None` -impl Clone for Handler { - fn clone(&self) -> Self { - Handler { - msg_input: self.msg_input.clone(), - client_connection_tx: self.client_connection_tx.clone(), - buffer_requester: self.buffer_requester.clone(), - self_full_address: self.self_full_address, - socket: None, - received_response_type: Default::default(), - lane_queue_lengths: self.lane_queue_lengths.clone(), - } - } + reply_controller_sender: ReplyControllerSender, } impl Drop for Handler { @@ -73,22 +101,46 @@ impl Drop for Handler { } impl Handler { - pub(crate) fn new( - msg_input: InputMessageSender, - client_connection_tx: ConnectionCommandSender, - buffer_requester: ReceivedBufferRequestSender, - self_full_address: Recipient, - lane_queue_lengths: LaneQueueLengths, - ) -> Self { - Handler { - msg_input, - client_connection_tx, - buffer_requester, - self_full_address, - socket: None, - received_response_type: Default::default(), - lane_queue_lengths, + async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> Option { + let req_start = Instant::now(); + + // get the base queue length + // Note that this does _NOT_ take into account the packets that have been received but not + // yet reach `OutQueueControl`, so it might be a tad low. + let conn_lane = TransmissionLane::ConnectionId(connection_id); + let Ok(base_length) = self + .lane_queue_lengths + .lock() + .map(|guard| guard.get(&conn_lane).unwrap_or_default()) else { + // I'd argue we should panic here as this error it not recoverable + error!("The lane queue length lock is poisoned!!"); + return None + }; + + // get the number of pending replies waiting for reply surbs + let reply_queue_length = self + .reply_controller_sender + .get_lane_queue_length(connection_id) + .await; + + let queue_length = base_length + reply_queue_length; + + let time_taken = req_start.elapsed(); + let msg = + format!("it took {time_taken:?} to get lane length for connection {connection_id}. The length is: {queue_length} = {base_length} (already queued up) + {reply_queue_length} (waiting for reply SURBs)"); + + if time_taken > Duration::from_millis(1) { + info!("{msg}"); + } else if time_taken > Duration::from_millis(10) { + warn!("{msg}"); + } else if time_taken > Duration::from_millis(50) { + error!("{msg}"); } + + Some(ServerResponse::LaneQueueLength { + lane: connection_id, + queue_length, + }) } async fn handle_send( @@ -115,27 +167,11 @@ impl Handler { .expect("InputMessageReceiver has stopped receiving!"); // Only reply back with a `LaneQueueLength` if the sender providided a connection id - let connection_id = match lane { - TransmissionLane::General - | TransmissionLane::ReplySurbRequest - | TransmissionLane::Retransmission - | TransmissionLane::AdditionalReplySurbs => return None, - TransmissionLane::ConnectionId(id) => id, + let TransmissionLane::ConnectionId(connection_id) = lane else { + return None }; - // on receiving a send, we reply back the current lane queue length for that connection id. - // Note that this does _NOT_ take into account the packets that have been received but not - // yet reach `OutQueueControl`, so it might be a tad low. - if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() { - let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); - return Some(ServerResponse::LaneQueueLength { - lane: connection_id, - queue_length, - }); - } - - log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length"); - None + self.get_lane_queue_length(connection_id).await } async fn handle_send_anonymous( @@ -162,27 +198,11 @@ impl Handler { .expect("InputMessageReceiver has stopped receiving!"); // Only reply back with a `LaneQueueLength` if the sender providided a connection id - let connection_id = match lane { - TransmissionLane::General - | TransmissionLane::ReplySurbRequest - | TransmissionLane::Retransmission - | TransmissionLane::AdditionalReplySurbs => return None, - TransmissionLane::ConnectionId(id) => id, + let TransmissionLane::ConnectionId(connection_id) = lane else { + return None }; - // on receiving a send, we reply back the current lane queue length for that connection id. - // Note that this does _NOT_ take into account the packets that have been received but not - // yet reach `OutQueueControl`, so it might be a tad low. - if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() { - let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); - return Some(ServerResponse::LaneQueueLength { - lane: connection_id, - queue_length, - }); - } - - log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length"); - None + self.get_lane_queue_length(connection_id).await } async fn handle_reply( @@ -205,27 +225,11 @@ impl Handler { .expect("InputMessageReceiver has stopped receiving!"); // Only reply back with a `LaneQueueLength` if the sender providided a connection id - let connection_id = match lane { - TransmissionLane::General - | TransmissionLane::ReplySurbRequest - | TransmissionLane::Retransmission - | TransmissionLane::AdditionalReplySurbs => return None, - TransmissionLane::ConnectionId(id) => id, + let TransmissionLane::ConnectionId(connection_id) = lane else { + return None }; - // on receiving a send, we reply back the current lane queue length for that connection id. - // Note that this does _NOT_ take into account the packets that have been received but not - // yet reach `OutQueueControl`, so it might be a tad low. - if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() { - let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); - return Some(ServerResponse::LaneQueueLength { - lane: connection_id, - queue_length, - }); - } - - log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length"); - None + self.get_lane_queue_length(connection_id).await } fn handle_self_address(&self) -> ServerResponse { @@ -239,20 +243,8 @@ impl Handler { None } - fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option { - let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() else { - log::warn!( - "Failed to get the lane queue length lock, not responding back with the current queue length" - ); - return None; - }; - - let lane = TransmissionLane::ConnectionId(connection_id); - let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); - Some(ServerResponse::LaneQueueLength { - lane: connection_id, - queue_length, - }) + async fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option { + self.get_lane_queue_length(connection_id).await } async fn handle_request(&mut self, request: ClientRequest) -> Option { @@ -281,7 +273,7 @@ impl Handler { ClientRequest::SelfAddress => Some(self.handle_self_address()), ClientRequest::ClosedConnection(id) => self.handle_closed_connection(id), - ClientRequest::GetLaneQueueLength(id) => self.handle_get_lane_queue_length(id), + ClientRequest::GetLaneQueueLength(id) => self.handle_get_lane_queue_length(id).await, } } @@ -362,8 +354,12 @@ impl Handler { } } - async fn listen_for_requests(&mut self, mut msg_receiver: ReconstructedMessagesReceiver) { - loop { + async fn listen_for_requests( + &mut self, + mut msg_receiver: ReconstructedMessagesReceiver, + mut task_client: nym_task::TaskClient, + ) { + while !task_client.is_shutdown() { tokio::select! { // we can either get a client request from the websocket socket_msg = self.next_websocket_request() => { @@ -373,7 +369,7 @@ impl Handler { let socket_msg = match socket_msg.unwrap() { Ok(socket_msg) => socket_msg, Err(err) => { - warn!("failed to obtain message from websocket stream! stopping connection handler: {}", err); + warn!("failed to obtain message from websocket stream! stopping connection handler: {err}"); break; } }; @@ -397,21 +393,33 @@ impl Handler { error!("mix messages sender was unexpectedly closed! this shouldn't have ever happened! (unless we're shutting down - TODO: implement proper graceful shutdown handler)"); return }; - if let Err(e) = self.push_websocket_received_plaintexts(mix_messages).await { - warn!("failed to send sphinx packets back to the client - {:?}, assuming the connection is dead", e); + if let Err(err) = self.push_websocket_received_plaintexts(mix_messages).await { + warn!("failed to send sphinx packets back to the client - {err}, assuming the connection is dead"); break; } } + _ = task_client.recv() => { + log::trace!("Websocket handler: Received shutdown"); + } } } + log::debug!("Websocket handler: Exiting"); } // consume self to make sure `drop` is called after this is done - pub(crate) async fn handle_connection(mut self, socket: TcpStream) { + pub(crate) async fn handle_connection( + mut self, + socket: TcpStream, + mut task_client: nym_task::TaskClient, + ) { + // We don't want a crash in the connection handler to trigger a shutdown of the whole + // process. + task_client.mark_as_success(); + let ws_stream = match accept_async(socket).await { Ok(ws_stream) => ws_stream, Err(err) => { - warn!("error while performing the websocket handshake - {:?}", err); + warn!("error while performing the websocket handshake - {err}"); return; } }; @@ -426,7 +434,8 @@ impl Handler { )) .expect("the buffer request failed!"); - self.listen_for_requests(reconstructed_receiver).await; + self.listen_for_requests(reconstructed_receiver, task_client) + .await; } } diff --git a/clients/native/src/websocket/listener.rs b/clients/native/src/websocket/listener.rs index c7825fb345..712389145a 100644 --- a/clients/native/src/websocket/listener.rs +++ b/clients/native/src/websocket/listener.rs @@ -1,8 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use super::handler::Handler; +use super::handler::HandlerBuilder; use log::*; +use std::net::IpAddr; use std::{net::SocketAddr, process, sync::Arc}; use tokio::io::AsyncWriteExt; use tokio::{sync::Notify, task::JoinHandle}; @@ -24,19 +25,22 @@ pub(crate) struct Listener { } impl Listener { - pub(crate) fn new(port: u16) -> Self { + pub(crate) fn new(host: IpAddr, port: u16) -> Self { Listener { - // unless we find compelling reason not to, just listen on local only - address: SocketAddr::new("127.0.0.1".parse().unwrap(), port), + address: SocketAddr::new(host, port), state: State::AwaitingConnection, } } - pub(crate) async fn run(&mut self, handler: Handler) { + pub(crate) async fn run( + &mut self, + handler: HandlerBuilder, + mut task_client: nym_task::TaskClient, + ) { let tcp_listener = match tokio::net::TcpListener::bind(self.address).await { Ok(listener) => listener, Err(err) => { - error!("Failed to bind to {} - {}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address, err); + error!("Failed to bind to {} - {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address); process::exit(1); } }; @@ -45,10 +49,23 @@ impl Listener { loop { tokio::select! { + // When the handler finishes we check if shutdown is signalled _ = notify.notified() => { + if task_client.is_shutdown() { + log::trace!("Websocket listener: detected shutdown after connection closed"); + break; + } // our connection terminated - we are open to a new one now! self.state = State::AwaitingConnection; } + // ... but when there is no connected client at the time of shutdown being + // signalled, we handle it here. + _ = task_client.recv() => { + if !self.state.is_connected() { + log::trace!("Not connected: shutting down"); + break; + } + } new_conn = tcp_listener.accept() => { match new_conn { Ok((mut socket, remote_addr)) => { @@ -63,31 +80,37 @@ impl Listener { Ok(_) => trace!( "closed the connection between attempting websocket handshake" ), - Err(e) => warn!("failed to cleanly close the connection - {:?}", e), + Err(err) => warn!("failed to cleanly close the connection - {err}"), }; } else { // even though we're spawning a new task with the handler here, we will only ever spawn a single one. // it's done so that any new connections to this listener could be rejected rather than left // hanging because the executor doesn't come back here let notify_clone = Arc::clone(¬ify); - let fresh_handler = handler.clone(); + let fresh_handler = handler.create_active_handler(); + let task_client_handler = task_client.clone(); tokio::spawn(async move { - fresh_handler.handle_connection(socket).await; + fresh_handler.handle_connection(socket, task_client_handler).await; notify_clone.notify_one(); }); self.state = State::Connected; } } - Err(e) => warn!("failed to get client: {:?}", e), + Err(err) => warn!("failed to get client: {err}"), } } } } + log::debug!("Websocket listener: Exiting"); } - pub(crate) fn start(mut self, handler: Handler) -> JoinHandle<()> { + pub(crate) fn start( + mut self, + handler: HandlerBuilder, + shutdown: nym_task::TaskClient, + ) -> JoinHandle<()> { info!("Running websocket on {:?}", self.address.to_string()); - tokio::spawn(async move { self.run(handler).await }) + tokio::spawn(async move { self.run(handler, shutdown).await }) } } diff --git a/clients/native/src/websocket/mod.rs b/clients/native/src/websocket/mod.rs index bd17b6ee08..38dadbd005 100644 --- a/clients/native/src/websocket/mod.rs +++ b/clients/native/src/websocket/mod.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub(crate) use handler::Handler; +pub(crate) use handler::HandlerBuilder; pub(crate) use listener::Listener; pub(crate) mod handler; diff --git a/clients/native/websocket-requests/Cargo.toml b/clients/native/websocket-requests/Cargo.toml index a2689476bc..0f0921f636 100644 --- a/clients/native/websocket-requests/Cargo.toml +++ b/clients/native/websocket-requests/Cargo.toml @@ -10,4 +10,4 @@ edition = "2021" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -nymsphinx = { path = "../../../common/nymsphinx" } +nym-sphinx = { path = "../../../common/nymsphinx" } diff --git a/clients/native/websocket-requests/src/error.rs b/clients/native/websocket-requests/src/error.rs index 2d9035c19d..67afaddc9b 100644 --- a/clients/native/websocket-requests/src/error.rs +++ b/clients/native/websocket-requests/src/error.rs @@ -84,7 +84,7 @@ impl TryFrom for ErrorKind { n => Err(Error::new( ErrorKind::MalformedResponse, - format!("invalid error code {}", n), + format!("invalid error code {n}"), )), } } diff --git a/clients/native/websocket-requests/src/requests.rs b/clients/native/websocket-requests/src/requests.rs index f50364a3cf..1e72ea8cfe 100644 --- a/clients/native/websocket-requests/src/requests.rs +++ b/clients/native/websocket-requests/src/requests.rs @@ -6,8 +6,8 @@ use crate::error::{self, ErrorKind}; use crate::text::ClientRequestText; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE}; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE}; use std::convert::{TryFrom, TryInto}; use std::mem::size_of; @@ -132,7 +132,7 @@ impl ClientRequest { Err(err) => { return Err(error::Error::new( ErrorKind::MalformedRequest, - format!("malformed recipient: {:?}", err), + format!("malformed recipient: {err}"), )) } }; @@ -210,7 +210,7 @@ impl ClientRequest { Err(err) => { return Err(error::Error::new( ErrorKind::MalformedRequest, - format!("malformed recipient: {:?}", err), + format!("malformed recipient: {err}"), )) } }; diff --git a/clients/native/websocket-requests/src/responses.rs b/clients/native/websocket-requests/src/responses.rs index cbefe54242..bf087d04f3 100644 --- a/clients/native/websocket-requests/src/responses.rs +++ b/clients/native/websocket-requests/src/responses.rs @@ -6,9 +6,9 @@ use crate::error::{self, ErrorKind}; use crate::text::ServerResponseText; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE}; -use nymsphinx::receiver::ReconstructedMessage; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE}; +use nym_sphinx::receiver::ReconstructedMessage; use std::convert::TryInto; use std::mem::size_of; @@ -173,7 +173,7 @@ impl ServerResponse { Err(err) => { return Err(error::Error::new( ErrorKind::MalformedResponse, - format!("malformed Recipient: {:?}", err), + format!("malformed Recipient: {err}"), )) } }; @@ -249,7 +249,7 @@ impl ServerResponse { Err(err) => { return Err(error::Error::new( ErrorKind::MalformedResponse, - format!("malformed error message: {:?}", err), + format!("malformed error message: {err}"), )) } }; diff --git a/clients/native/websocket-requests/src/text.rs b/clients/native/websocket-requests/src/text.rs index 68091dbecf..944a3855fa 100644 --- a/clients/native/websocket-requests/src/text.rs +++ b/clients/native/websocket-requests/src/text.rs @@ -4,8 +4,8 @@ use crate::error::ErrorKind; use crate::requests::ClientRequest; use crate::responses::ServerResponse; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index add5a3ee58..a9e131f778 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.4" +version = "1.1.13" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" @@ -11,46 +11,44 @@ name = "nym_socks5" path = "src/lib.rs" [dependencies] -clap = { version = "3.2", features = ["cargo", "derive"] } +clap = { version = "4.0", features = ["cargo", "derive"] } dirs = "4.0" futures = "0.3" -log = "0.4" +lazy_static = "1.4.0" +log = { workspace = true } pin-project = "1.0" pretty_env_logger = "0.4" rand = { version = "0.7.3", features = ["wasm-bindgen"] } -serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization -serde_json = "1.0.89" +serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization +serde_json = { workspace = true } tap = "1.0.1" thiserror = "1.0.34" -tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } +tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] } url = "2.2" # internal +nym-bin-common = { path = "../../common/bin-common" } client-core = { path = "../client-core", features = ["fs-surb-storage"] } -client-connections = { path = "../../common/client-connections" } -coconut-interface = { path = "../../common/coconut-interface", optional = true } -config = { path = "../../common/config" } -completions = { path = "../../common/completions" } -credential-storage = { path = "../../common/credential-storage" } -credentials = { path = "../../common/credentials", optional = true } -crypto = { path = "../../common/crypto" } -logging = { path = "../../common/logging"} +nym-coconut-interface = { path = "../../common/coconut-interface" } +nym-config = { path = "../../common/config" } +nym-credential-storage = { path = "../../common/credential-storage", optional = true } +mobile-storage = { path = "../../common/mobile-storage", optional = true } +nym-credentials = { path = "../../common/credentials" } +nym-crypto = { path = "../../common/crypto" } gateway-client = { path = "../../common/client-libs/gateway-client" } gateway-requests = { path = "../../gateway/gateway-requests" } -network-defaults = { path = "../../common/network-defaults" } -nymsphinx = { path = "../../common/nymsphinx" } -ordered-buffer = { path = "../../common/socks5/ordered-buffer" } -pemstore = { path = "../../common/pemstore" } -proxy-helpers = { path = "../../common/socks5/proxy-helpers" } -socks5-requests = { path = "../../common/socks5/requests" } -task = { path = "../../common/task" } -topology = { path = "../../common/topology" } -validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] } -version-checker = { path = "../../common/version-checker" } +nym-network-defaults = { path = "../../common/network-defaults" } +nym-sphinx = { path = "../../common/nymsphinx" } +nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" } +nym-pemstore = { path = "../../common/pemstore" } +nym-socks5-proxy-helpers = { path = "../../common/socks5/proxy-helpers" } +nym-service-providers-common = { path = "../../service-providers/common" } +nym-socks5-requests = { path = "../../common/socks5/requests" } +nym-task = { path = "../../common/task" } +nym-topology = { path = "../../common/topology" } +validator-client = { path = "../../common/client-libs/validator-client", features = ["nyxd-client"] } [features] -coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"] +default = ["nym-credential-storage"] eth = [] - -[build-dependencies] -vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] } +mobile = ["mobile-storage", "gateway-client/mobile"] diff --git a/clients/socks5/build.rs b/clients/socks5/build.rs deleted file mode 100644 index 01b3a20dc6..0000000000 --- a/clients/socks5/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use vergen::{vergen, Config}; - -fn main() { - vergen(Config::default()).expect("failed to extract build metadata") -} diff --git a/clients/socks5/src/client/config/mod.rs b/clients/socks5/src/client/config/mod.rs index cda73334d3..cf974199a3 100644 --- a/clients/socks5/src/client/config/mod.rs +++ b/clients/socks5/src/client/config/mod.rs @@ -1,21 +1,26 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::client::config::template::config_template; +pub use client_core::config::Config as BaseConfig; pub use client_core::config::MISSING_VALUE; -use client_core::config::{ClientCoreConfigTrait, Config as BaseConfig, DebugConfig}; -use config::defaults::DEFAULT_SOCKS5_LISTENING_PORT; -use config::NymConfig; -use nymsphinx::addressing::clients::Recipient; +use client_core::config::{ClientCoreConfigTrait, DebugConfig}; +use nym_config::defaults::DEFAULT_SOCKS5_LISTENING_PORT; +use nym_config::{NymConfig, OptionalSet}; +use nym_service_providers_common::interface::ProviderInterfaceVersion; +use nym_socks5_requests::Socks5ProtocolVersion; +use nym_sphinx::addressing::clients::Recipient; use serde::{Deserialize, Serialize}; +use std::fmt::Debug; use std::path::PathBuf; +use std::str::FromStr; mod template; const DEFAULT_CONNECTION_START_SURBS: u32 = 20; const DEFAULT_PER_REQUEST_SURBS: u32 = 3; -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { #[serde(flatten)] @@ -33,10 +38,12 @@ impl NymConfig for Config { } fn default_root_directory() -> PathBuf { - dirs::home_dir() - .expect("Failed to evaluate $HOME value") - .join(".nym") - .join("socks5-clients") + #[cfg(not(feature = "mobile"))] + let base_dir = dirs::home_dir().expect("Failed to evaluate $HOME value"); + #[cfg(feature = "mobile")] + let base_dir = PathBuf::from("/tmp"); + + base_dir.join(".nym").join("socks5-clients") } fn try_default_root_directory() -> Option { @@ -85,6 +92,16 @@ impl Config { self } + pub fn with_provider_interface_version(mut self, version: ProviderInterfaceVersion) -> Self { + self.socks5.provider_interface_version = version; + self + } + + pub fn with_socks5_protocol_version(mut self, version: Socks5ProtocolVersion) -> Self { + self.socks5.socks5_protocol_version = version; + self + } + pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self { self.socks5.send_anonymously = anonymous_replies; self @@ -112,6 +129,14 @@ impl Config { .expect("malformed provider address") } + pub fn get_provider_interface_version(&self) -> ProviderInterfaceVersion { + self.socks5.provider_interface_version + } + + pub fn get_socks5_protocol_version(&self) -> Socks5ProtocolVersion { + self.socks5.socks5_protocol_version + } + pub fn get_send_anonymously(&self) -> bool { self.socks5.send_anonymously } @@ -127,9 +152,53 @@ impl Config { pub fn get_per_request_surbs(&self) -> u32 { self.socks5_debug.per_request_surbs } + + // poor man's 'builder' method + pub fn with_base(mut self, f: F, val: T) -> Self + where + F: Fn(BaseConfig, T) -> BaseConfig, + { + self.base = f(self.base, val); + self + } + + // helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config + // (plz, lets refactor it) + pub fn with_optional_ext(mut self, f: F, val: Option) -> Self + where + F: Fn(BaseConfig, T) -> BaseConfig, + { + self.base = self.base.with_optional(f, val); + self + } + + pub fn with_optional_env_ext(mut self, f: F, val: Option, env_var: &str) -> Self + where + F: Fn(BaseConfig, T) -> BaseConfig, + T: FromStr, + ::Err: Debug, + { + self.base = self.base.with_optional_env(f, val, env_var); + self + } + + pub fn with_optional_custom_env_ext( + mut self, + f: F, + val: Option, + env_var: &str, + parser: G, + ) -> Self + where + F: Fn(BaseConfig, T) -> BaseConfig, + G: Fn(&str) -> T, + { + self.base = self.base.with_optional_custom_env(f, val, env_var, parser); + self + } } -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] pub struct Socks5 { /// The port on which the client will be listening for incoming requests @@ -138,6 +207,15 @@ pub struct Socks5 { /// The mix address of the provider to which all requests are going to be sent. provider_mix_address: String, + /// The version of the 'service provider' this client is going to use in its communication with the + /// specified socks5 provider. + // if in doubt, use the legacy version as initially nobody will be using the updated binaries + #[serde(default = "ProviderInterfaceVersion::new_legacy")] + provider_interface_version: ProviderInterfaceVersion, + + #[serde(default = "Socks5ProtocolVersion::new_legacy")] + socks5_protocol_version: Socks5ProtocolVersion, + /// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. /// While this is going to hide its actual address information, it will make the actual communication /// slower and consume nearly double the bandwidth as it will require sending reply SURBs. @@ -152,6 +230,8 @@ impl Socks5 { Socks5 { listening_port: DEFAULT_SOCKS5_LISTENING_PORT, provider_mix_address: provider_mix_address.into(), + provider_interface_version: ProviderInterfaceVersion::Legacy, + socks5_protocol_version: Socks5ProtocolVersion::Legacy, send_anonymously: false, } } @@ -162,12 +242,14 @@ impl Default for Socks5 { Socks5 { listening_port: DEFAULT_SOCKS5_LISTENING_PORT, provider_mix_address: "".into(), + provider_interface_version: ProviderInterfaceVersion::Legacy, + socks5_protocol_version: Socks5ProtocolVersion::Legacy, send_anonymously: false, } } } -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] pub struct Socks5Debug { /// Number of reply SURBs attached to each `Request::Connect` message. diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs index 527da38274..f8c274c27c 100644 --- a/clients/socks5/src/client/config/template.rs +++ b/clients/socks5/src/client/config/template.rs @@ -23,16 +23,16 @@ id = '{{ client.id }}' # to claim bandwidth without presenting bandwidth credentials. disabled_credentials_mode = {{ client.disabled_credentials_mode }} -# Addresses to nymd validators via which the client can communicate with the chain. -validator_urls = [ - {{#each client.validator_urls }} +# Addresses to nyxd validators via which the client can communicate with the chain. +nyxd_urls = [ + {{#each client.nyxd_urls }} '{{this}}', {{/each}} ] # Addresses to APIs running on validator from which the client gets the view of the network. -validator_api_urls = [ - {{#each client.validator_api_urls }} +nym_api_urls = [ + {{#each client.nym_api_urls }} '{{this}}', {{/each}} ] diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index 2b4bff7852..50d0718ca7 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -8,18 +8,23 @@ use crate::socks::{ authentication::{AuthenticationMethods, Authenticator, User}, server::SphinxSocksServer, }; -use client_core::client::base_client::{ - non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, -}; + +#[cfg(feature = "mobile")] +use client_core::client::base_client::helpers::setup_empty_reply_surb_backend; +#[cfg(not(feature = "mobile"))] +use client_core::client::base_client::non_wasm_helpers; +use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput, ClientState}; use client_core::client::key_manager::KeyManager; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use futures::channel::mpsc; use futures::StreamExt; +#[cfg(not(feature = "mobile"))] use gateway_client::bandwidth::BandwidthController; use log::*; -use nymsphinx::addressing::clients::Recipient; +use nym_sphinx::addressing::clients::Recipient; +use nym_task::{TaskClient, TaskManager}; use std::error::Error; -use task::{wait_for_signal_and_error, ShutdownListener, ShutdownNotifier}; +use validator_client::nyxd::QueryNyxdClient; pub mod config; @@ -53,62 +58,73 @@ impl NymClient { } } - async fn create_bandwidth_controller(config: &Config) -> BandwidthController { - #[cfg(feature = "coconut")] - let bandwidth_controller = { - let details = network_defaults::NymNetworkDetails::new_from_env(); - let mut client_config = - validator_client::Config::try_from_nym_network_details(&details) - .expect("failed to construct validator client config"); - let nymd_url = config - .get_base() - .get_validator_endpoints() - .pop() - .expect("No nymd validator endpoint provided"); - let api_url = config - .get_base() - .get_validator_api_endpoints() - .pop() - .expect("No validator api endpoint provided"); - // overwrite env configuration with config URLs - client_config = client_config.with_urls(nymd_url, api_url); - let client = validator_client::Client::new_query(client_config) - .expect("Could not construct query client"); - let coconut_api_clients = - validator_client::CoconutApiClient::all_coconut_api_clients(&client) - .await - .expect("Could not query api clients"); - BandwidthController::new( - credential_storage::initialise_storage(config.get_base().get_database_path()).await, - coconut_api_clients, - ) - }; - #[cfg(not(feature = "coconut"))] - let bandwidth_controller = BandwidthController::new( - credential_storage::initialise_storage(config.get_base().get_database_path()).await, - ) - .expect("Could not create bandwidth controller"); - bandwidth_controller + pub fn new_with_keys(config: Config, key_manager: Option) -> Self { + let key_manager = key_manager.unwrap_or_else(|| { + let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); + KeyManager::load_keys(&pathfinder).expect("failed to load stored keys") + }); + + NymClient { + config, + key_manager, + } + } + + #[cfg(not(feature = "mobile"))] + async fn create_bandwidth_controller(config: &Config) -> BandwidthController { + let details = nym_network_defaults::NymNetworkDetails::new_from_env(); + let mut client_config = validator_client::Config::try_from_nym_network_details(&details) + .expect("failed to construct validator client config"); + let nyxd_url = config + .get_base() + .get_validator_endpoints() + .pop() + .expect("No nyxd validator endpoint provided"); + let api_url = config + .get_base() + .get_nym_api_endpoints() + .pop() + .expect("No validator api endpoint provided"); + // overwrite env configuration with config URLs + client_config = client_config.with_urls(nyxd_url, api_url); + let client = validator_client::Client::new_query(client_config) + .expect("Could not construct query client"); + + #[cfg(not(feature = "mobile"))] + let storage = + nym_credential_storage::initialise_storage(config.get_base().get_database_path()).await; + + #[cfg(feature = "mobile")] + let storage = mobile_storage::PersistentStorage {}; + + BandwidthController::new(storage, client) } fn start_socks5_listener( config: &Config, client_input: ClientInput, client_output: ClientOutput, + client_status: ClientState, self_address: Recipient, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { info!("Starting socks5 listener..."); let auth_methods = vec![AuthenticationMethods::NoAuth as u8]; let allowed_users: Vec = Vec::new(); let ClientInput { - shared_lane_queue_lengths, connection_command_sender, input_sender, } = client_input; - let received_buffer_request_sender = client_output.received_buffer_request_sender; + let ClientOutput { + received_buffer_request_sender, + } = client_output; + + let ClientState { + shared_lane_queue_lengths, + .. + } = client_status; let authenticator = Authenticator::new(auth_methods, allowed_users); let mut sphinx_socks = SphinxSocksServer::new( @@ -118,13 +134,15 @@ impl NymClient { self_address, shared_lane_queue_lengths, socks::client::Config::new( + config.get_provider_interface_version(), + config.get_socks5_protocol_version(), config.get_send_anonymously(), config.get_connection_start_surbs(), config.get_per_request_surbs(), ), shutdown.clone(), ); - task::spawn_with_report_error( + nym_task::spawn_with_report_error( async move { sphinx_socks .serve( @@ -140,16 +158,9 @@ impl NymClient { /// blocking version of `start` method. Will run forever (or until SIGINT is sent) pub async fn run_forever(self) -> Result<(), Box> { - let mut shutdown = self.start().await?; - - let res = wait_for_signal_and_error(&mut shutdown).await; - - log::info!("Sending shutdown"); - shutdown.signal_shutdown().ok(); - - log::info!("Waiting for tasks to finish... (Press ctrl-c to force)"); - shutdown.wait_for_shutdown().await; + let shutdown = self.start().await?; + let res = shutdown.catch_interrupt().await; log::info!("Stopping nym-socks5-client"); res } @@ -158,13 +169,13 @@ impl NymClient { pub async fn run_and_listen( self, mut receiver: Socks5ControlMessageReceiver, - sender: task::StatusSender, + sender: nym_task::StatusSender, ) -> Result<(), Box> { // Start the main task let mut shutdown = self.start().await?; // Listen to status messages from task, that we forward back to the caller - shutdown.start_status_listener(sender); + shutdown.start_status_listener(sender).await; let res = tokio::select! { biased; @@ -200,34 +211,45 @@ impl NymClient { res } - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { + #[cfg(not(feature = "mobile"))] let base_builder = BaseClientBuilder::new_from_base_config( self.config.get_base(), self.key_manager, Some(Self::create_bandwidth_controller(&self.config).await), non_wasm_helpers::setup_fs_reply_surb_backend( - self.config.get_base().get_reply_surb_database_path(), + Some(self.config.get_base().get_reply_surb_database_path()), self.config.get_debug_settings(), ) .await?, ); + #[cfg(feature = "mobile")] + let base_builder = BaseClientBuilder::<_, QueryNyxdClient>::new_from_base_config( + self.config.get_base(), + self.key_manager, + None, + setup_empty_reply_surb_backend(self.config.get_debug_settings()), + ); + let self_address = base_builder.as_mix_recipient(); let mut started_client = base_builder.start_base().await?; let client_input = started_client.client_input.register_producer(); let client_output = started_client.client_output.register_consumer(); + let client_state = started_client.client_state; Self::start_socks5_listener( &self.config, client_input, client_output, + client_state, self_address, - started_client.shutdown_notifier.subscribe(), + started_client.task_manager.subscribe(), ); info!("Client startup finished!"); info!("The address of this client is: {}", self_address); - Ok(started_client.shutdown_notifier) + Ok(started_client.task_manager) } } diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 70e8935ca3..c921834ec3 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -7,8 +7,9 @@ use crate::{ error::Socks5ClientError, }; use clap::Args; -use config::NymConfig; -use nymsphinx::addressing::clients::Recipient; +use nym_config::NymConfig; +use nym_crypto::asymmetric::identity; +use nym_sphinx::addressing::clients::Recipient; use serde::Serialize; use std::fmt::Display; use tap::TapFallible; @@ -21,32 +22,39 @@ pub(crate) struct Init { /// Address of the socks5 provider to send messages to. #[clap(long)] - provider: String, + provider: Recipient, /// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. /// While this is going to hide its actual address information, it will make the actual communication /// slower and consume nearly double the bandwidth as it will require sending reply SURBs. /// /// Note that some service providers might not support this. - #[clap(long)] - use_anonymous_sender_tag: bool, + // the alias here is included for backwards compatibility (1.1.4 and before) + #[clap(long, alias = "use_anonymous_sender_tag")] + use_reply_surbs: Option, /// Id of the gateway we are going to connect to. #[clap(long)] - gateway: Option, + gateway: Option, + + /// Specifies whether the new gateway should be determined based by latency as opposed to being chosen + /// uniformly. + #[clap(long, conflicts_with = "gateway")] + latency_based_selection: bool, /// Force register gateway. WARNING: this will overwrite any existing keys for the given id, /// potentially causing loss of access. #[clap(long)] force_register_gateway: bool, - /// Comma separated list of rest endpoints of the nymd validators - #[clap(long)] - nymd_validators: Option, + /// Comma separated list of rest endpoints of the nyxd validators + #[clap(long, alias = "nyxd_validators", value_delimiter = ',', hide = true)] + nyxd_urls: Option>, /// Comma separated list of rest endpoints of the API validators - #[clap(long)] - api_validators: Option, + #[clap(long, alias = "api_validators", value_delimiter = ',')] + // the alias here is included for backwards compatibility (1.1.4 and before) + nym_apis: Option>, /// Port for the socket to listen on in all subsequent runs #[clap(short, long)] @@ -54,18 +62,17 @@ pub(crate) struct Init { /// Mostly debug-related option to increase default traffic rate so that you would not need to /// modify config post init - #[clap(long, hidden = true)] + #[clap(long, hide = true)] fastmode: bool, /// Disable loop cover traffic and the Poisson rate limiter (for debugging only) - #[clap(long, hidden = true)] + #[clap(long, hide = true)] no_cover: bool, /// Set this client to work in a enabled credentials mode that would attempt to use gateway /// with bandwidth credential requirement. - #[cfg(feature = "coconut")] - #[clap(long)] - enabled_credentials_mode: bool, + #[clap(long, hide = true)] + enabled_credentials_mode: Option, /// Save a summary of the initialization to a json file #[clap(long)] @@ -75,13 +82,12 @@ pub(crate) struct Init { impl From for OverrideConfig { fn from(init_config: Init) -> Self { OverrideConfig { - nymd_validators: init_config.nymd_validators, - api_validators: init_config.api_validators, + nym_apis: init_config.nym_apis, port: init_config.port, - use_anonymous_sender_tag: init_config.use_anonymous_sender_tag, + use_anonymous_replies: init_config.use_reply_surbs, fastmode: init_config.fastmode, no_cover: init_config.no_cover, - #[cfg(feature = "coconut")] + nyxd_urls: init_config.nyxd_urls, enabled_credentials_mode: init_config.enabled_credentials_mode, } } @@ -116,18 +122,17 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> { let id = &args.id; let provider_address = &args.provider; - let already_init = Config::default_config_file_path(Some(id)).exists(); + let already_init = Config::default_config_file_path(id).exists(); if already_init { - println!( - "SOCKS5 client \"{}\" was already initialised before! \ - Config information will be overwritten (but keys will be kept)!", - id - ); + println!("SOCKS5 client \"{id}\" was already initialised before"); } // Usually you only register with the gateway on the first init, however you can force // re-registering if wanted. let user_wants_force_register = args.force_register_gateway; + if user_wants_force_register { + println!("Instructed to force registering gateway. This might overwrite keys!"); + } // If the client was already initialized, don't generate new keys and don't re-register with // the gateway (because this would create a new shared key). @@ -135,25 +140,28 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> { let register_gateway = !already_init || user_wants_force_register; // Attempt to use a user-provided gateway, if possible - let user_chosen_gateway_id = args.gateway.clone(); + let user_chosen_gateway_id = args.gateway; // Load and potentially override config let mut config = override_config( - Config::new(id, provider_address), + Config::new(id, &provider_address.to_string()), OverrideConfig::from(args.clone()), ); // Setup gateway by either registering a new one, or creating a new config from the selected // one but with keys kept, or reusing the gateway configuration. - let gateway = client_core::init::setup_gateway::<_, Config, _>( + let gateway = client_core::init::setup_gateway_from_config::( register_gateway, user_chosen_gateway_id, config.get_base(), + args.latency_based_selection, ) .await .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; - config.get_base_mut().with_gateway_endpoint(gateway); + config.get_base_mut().set_gateway_endpoint(gateway); + + // TODO: ask the service provider we specified for its interface version and set it in the config config.save_to_file(None).tap_err(|_| { log::error!("Failed to save the config file"); diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 5f5bfda1de..9e5b903de6 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -1,55 +1,31 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::error::Error; - -use crate::client::config::Config; +use crate::client::config::{BaseConfig, Config}; use clap::CommandFactory; use clap::{Parser, Subcommand}; -use completions::{fig_generate, ArgShell}; -use config::parse_validators; +use lazy_static::lazy_static; +use nym_bin_common::build_information::BinaryBuildInformation; +use nym_bin_common::completions::{fig_generate, ArgShell}; +use nym_config::OptionalSet; +use std::error::Error; pub mod init; pub(crate) mod run; pub(crate) mod upgrade; -fn long_version() -> String { - format!( - r#" -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -"#, - "Build Timestamp:", - env!("VERGEN_BUILD_TIMESTAMP"), - "Build Version:", - env!("VERGEN_BUILD_SEMVER"), - "Commit SHA:", - env!("VERGEN_GIT_SHA"), - "Commit Date:", - env!("VERGEN_GIT_COMMIT_TIMESTAMP"), - "Commit Branch:", - env!("VERGEN_GIT_BRANCH"), - "rustc Version:", - env!("VERGEN_RUSTC_SEMVER"), - "rustc Channel:", - env!("VERGEN_RUSTC_CHANNEL"), - "cargo Profile:", - env!("VERGEN_CARGO_PROFILE"), - ) +lazy_static! { + pub static ref PRETTY_BUILD_INFORMATION: String = + BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print(); } -fn long_version_static() -> &'static str { - Box::leak(long_version().into_boxed_str()) +// Helper for passing LONG_VERSION to clap +fn pretty_build_info_static() -> &'static str { + &PRETTY_BUILD_INFORMATION } #[derive(Parser)] -#[clap(author = "Nymtech", version, long_version = long_version_static(), about)] +#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] pub(crate) struct Cli { /// Path pointing to an env file that configures the client. #[clap(short, long)] @@ -79,15 +55,13 @@ pub(crate) enum Commands { // Configuration that can be overridden. pub(crate) struct OverrideConfig { - nymd_validators: Option, - api_validators: Option, + nym_apis: Option>, port: Option, - use_anonymous_sender_tag: bool, + use_anonymous_replies: Option, fastmode: bool, no_cover: bool, - - #[cfg(feature = "coconut")] - enabled_credentials_mode: bool, + nyxd_urls: Option>, + enabled_credentials_mode: Option, } pub(crate) async fn execute(args: &Cli) -> Result<(), Box> { @@ -97,56 +71,34 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box init::execute(m).await?, Commands::Run(m) => run::execute(m).await?, Commands::Upgrade(m) => upgrade::execute(m), - Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name), - Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name), + Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name), + Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), } Ok(()) } -pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config { - if let Some(raw_validators) = args.nymd_validators { - config - .get_base_mut() - .set_custom_validators(parse_validators(&raw_validators)); - } else if let Ok(raw_validators) = std::env::var(network_defaults::var_names::NYMD_VALIDATOR) { - config - .get_base_mut() - .set_custom_validators(parse_validators(&raw_validators)); - } - if let Some(raw_validators) = args.api_validators { - config - .get_base_mut() - .set_custom_validator_apis(parse_validators(&raw_validators)); - } else if let Ok(raw_validators) = std::env::var(network_defaults::var_names::API_VALIDATOR) { - config - .get_base_mut() - .set_custom_validator_apis(parse_validators(&raw_validators)); - } - - if args.use_anonymous_sender_tag { - config = config.with_anonymous_replies(true) - } - - if let Some(port) = args.port { - config = config.with_port(port); - } - - #[cfg(feature = "coconut")] - { - if args.enabled_credentials_mode { - config.get_base_mut().with_disabled_credentials(false) - } - } - - if args.fastmode { - config.get_base_mut().set_high_default_traffic_volume(); - } - - if args.no_cover { - config.get_base_mut().set_no_cover_traffic(); - } - +pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { config + .with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode) + .with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover) + .with_optional(Config::with_anonymous_replies, args.use_anonymous_replies) + .with_optional(Config::with_port, args.port) + .with_optional_custom_env_ext( + BaseConfig::with_custom_nym_apis, + args.nym_apis, + nym_network_defaults::var_names::NYM_API, + nym_config::parse_urls, + ) + .with_optional_custom_env_ext( + BaseConfig::with_custom_nyxd, + args.nyxd_urls, + nym_network_defaults::var_names::NYXD, + nym_config::parse_urls, + ) + .with_optional_ext( + BaseConfig::with_disabled_credentials, + args.enabled_credentials_mode.map(|b| !b), + ) } #[cfg(test)] diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index 48d4651d15..6bd381b678 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -8,9 +8,11 @@ use crate::{ }; use clap::Args; -use config::NymConfig; use log::*; -use version_checker::is_minor_version_compatible; +use nym_bin_common::version_checker::is_minor_version_compatible; +use nym_config::NymConfig; +use nym_crypto::asymmetric::identity; +use nym_sphinx::addressing::clients::Recipient; #[derive(Args, Clone)] pub(crate) struct Run { @@ -27,25 +29,26 @@ pub(crate) struct Run { /// slower and consume nearly double the bandwidth as it will require sending reply SURBs. /// /// Note that some service providers might not support this. - #[clap(long)] - use_anonymous_sender_tag: bool, + // the alias here is included for backwards compatibility (1.1.4 and before) + #[clap(long, alias = "use_anonymous_sender_tag")] + use_anonymous_replies: Option, /// Address of the socks5 provider to send messages to. #[clap(long)] - provider: Option, + provider: Option, /// Id of the gateway we want to connect to. If overridden, it is user's responsibility to /// ensure prior registration happened #[clap(long)] - gateway: Option, + gateway: Option, - /// Comma separated list of rest endpoints of the nymd validators - #[clap(long)] - nymd_validators: Option, + /// Comma separated list of rest endpoints of the nyxd validators + #[clap(long, alias = "nyxd_validators", value_delimiter = ',', hide = true)] + nyxd_urls: Option>, - /// Comma separated list of rest endpoints of the API validators - #[clap(long)] - api_validators: Option, + /// Comma separated list of rest endpoints of the Nym APIs + #[clap(long, value_delimiter = ',')] + nym_apis: Option>, /// Port for the socket to listen on #[clap(short, long)] @@ -53,30 +56,28 @@ pub(crate) struct Run { /// Mostly debug-related option to increase default traffic rate so that you would not need to /// modify config post init - #[clap(long, hidden = true)] + #[clap(long, hide = true)] fastmode: bool, /// Disable loop cover traffic and the Poisson rate limiter (for debugging only) - #[clap(long, hidden = true)] + #[clap(long, hide = true)] no_cover: bool, /// Set this client to work in a enabled credentials mode that would attempt to use gateway /// with bandwidth credential requirement. - #[cfg(feature = "coconut")] - #[clap(long)] - enabled_credentials_mode: bool, + #[clap(long, hide = true)] + enabled_credentials_mode: Option, } impl From for OverrideConfig { fn from(run_config: Run) -> Self { OverrideConfig { - nymd_validators: run_config.nymd_validators, - api_validators: run_config.api_validators, + nym_apis: run_config.nym_apis, port: run_config.port, - use_anonymous_sender_tag: run_config.use_anonymous_sender_tag, + use_anonymous_replies: run_config.use_anonymous_replies, fastmode: run_config.fastmode, no_cover: run_config.no_cover, - #[cfg(feature = "coconut")] + nyxd_urls: run_config.nyxd_urls, enabled_credentials_mode: run_config.enabled_credentials_mode, } } @@ -107,10 +108,10 @@ fn version_check(cfg: &Config) -> bool { pub(crate) async fn execute(args: &Run) -> Result<(), Box> { let id = &args.id; - let mut config = match Config::load_from_file(Some(id)) { + let mut config = match Config::load_from_file(id) { Ok(cfg) => cfg, Err(err) => { - error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err); + error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id); return Err(Box::new(Socks5ClientError::FailedToLoadConfig( id.to_string(), ))); diff --git a/clients/socks5/src/commands/upgrade.rs b/clients/socks5/src/commands/upgrade.rs index ff41f3ce6e..982bae4634 100644 --- a/clients/socks5/src/commands/upgrade.rs +++ b/clients/socks5/src/commands/upgrade.rs @@ -3,8 +3,8 @@ use crate::client::config::{Config, MISSING_VALUE}; -use config::NymConfig; -use version_checker::Version; +use nym_bin_common::version_checker::Version; +use nym_config::NymConfig; use clap::Args; use std::{fmt::Display, process}; @@ -58,7 +58,7 @@ pub(crate) struct Upgrade { fn parse_config_version(config: &Config) -> Version { let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| { - eprintln!("failed to parse client version! - {:?}", err); + eprintln!("failed to parse client version! - {err}"); process::exit(1) }); @@ -109,7 +109,7 @@ fn minor_0_12_upgrade( .set_custom_version(to_version.to_string().as_ref()); config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); + eprintln!("failed to overwrite config file! - {err}"); print_failed_upgrade(config_version, &to_version); process::exit(1); }); @@ -144,8 +144,8 @@ pub(crate) fn execute(args: &Upgrade) { let id = &args.id; - let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { - eprintln!("failed to load existing config file! - {:?}", err); + let existing_config = Config::load_from_file(id).unwrap_or_else(|err| { + eprintln!("failed to load existing config file! - {err}"); process::exit(1) }); diff --git a/clients/socks5/src/error.rs b/clients/socks5/src/error.rs index 23cd715cf5..4504511ec2 100644 --- a/clients/socks5/src/error.rs +++ b/clients/socks5/src/error.rs @@ -1,6 +1,6 @@ use crate::socks::types::SocksProxyError; -use client_core::client::replies::reply_storage::fs_backend; use client_core::error::ClientCoreError; +use nym_socks5_requests::{ConnectionError, ConnectionId}; #[derive(thiserror::Error, Debug)] pub enum Socks5ClientError { @@ -8,7 +8,7 @@ pub enum Socks5ClientError { IoError(#[from] std::io::Error), #[error("client-core error: {0}")] - ClientCoreError(#[from] ClientCoreError), + ClientCoreError(#[from] ClientCoreError), #[error("SOCKS proxy error")] SocksProxyError(SocksProxyError), @@ -21,4 +21,19 @@ pub enum Socks5ClientError { #[error("Fail to bind address")] FailToBindAddress, + + #[error("Network requester: connection id {connection_id}: {error}")] + NetworkRequesterError { + connection_id: ConnectionId, + error: String, + }, +} + +impl From for Socks5ClientError { + fn from(value: ConnectionError) -> Self { + Socks5ClientError::NetworkRequesterError { + connection_id: value.connection_id, + error: value.network_requester_error, + } + } } diff --git a/clients/socks5/src/main.rs b/clients/socks5/src/main.rs index ea0071f538..4630ebb5e4 100644 --- a/clients/socks5/src/main.rs +++ b/clients/socks5/src/main.rs @@ -3,9 +3,9 @@ use std::error::Error; -use clap::{crate_version, Parser}; -use logging::setup_logging; -use network_defaults::setup_env; +use clap::{crate_name, crate_version, Parser}; +use nym_bin_common::logging::{banner, setup_logging}; +use nym_network_defaults::setup_env; pub mod client; mod commands; @@ -15,26 +15,9 @@ pub mod socks; #[tokio::main] async fn main() -> Result<(), Box> { setup_logging(); - println!("{}", banner()); + println!("{}", banner(crate_name!(), crate_version!())); let args = commands::Cli::parse(); - setup_env(args.config_env_file.clone()); + setup_env(args.config_env_file.as_ref()); commands::execute(&args).await } - -fn banner() -> String { - format!( - r#" - - _ __ _ _ _ __ ___ - | '_ \| | | | '_ \ _ \ - | | | | |_| | | | | | | - |_| |_|\__, |_| |_| |_| - |___/ - - (socks5 proxy - version {:}) - - "#, - crate_version!() - ) -} diff --git a/clients/socks5/src/socks/client.rs b/clients/socks5/src/socks/client.rs index 3b35a4caf9..25f4e2d568 100644 --- a/clients/socks5/src/socks/client.rs +++ b/clients/socks5/src/socks/client.rs @@ -4,23 +4,26 @@ use super::authentication::{AuthenticationMethods, Authenticator, User}; use super::request::{SocksCommand, SocksRequest}; use super::types::{ResponseCodeV4, ResponseCodeV5, SocksProxyError}; use super::{SocksVersion, RESERVED, SOCKS4_VERSION, SOCKS5_VERSION}; -use client_connections::{LaneQueueLengths, TransmissionLane}; use client_core::client::inbound_messages::{InputMessage, InputMessageSender}; use futures::channel::mpsc; use futures::task::{Context, Poll}; use log::*; -use nymsphinx::addressing::clients::Recipient; -use pin_project::pin_project; -use proxy_helpers::connection_controller::{ +use nym_service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion}; +use nym_socks5_proxy_helpers::connection_controller::{ ConnectionReceiver, ControllerCommand, ControllerSender, }; -use proxy_helpers::proxy_runner::ProxyRunner; +use nym_socks5_proxy_helpers::proxy_runner::ProxyRunner; +use nym_socks5_requests::{ + ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request, +}; +use nym_sphinx::addressing::clients::Recipient; +use nym_task::connections::{LaneQueueLengths, TransmissionLane}; +use nym_task::TaskClient; +use pin_project::pin_project; use rand::RngCore; -use socks5_requests::{ConnectionId, Message, RemoteAddress, Request}; use std::io; use std::net::SocketAddr; use std::pin::Pin; -use task::ShutdownListener; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}; use tokio::{self, net::TcpStream}; @@ -128,6 +131,8 @@ impl AsyncWrite for StreamState { #[derive(Debug, Copy, Clone)] pub(crate) struct Config { + provider_interface_version: ProviderInterfaceVersion, + socks5_protocol_version: Socks5ProtocolVersion, use_surbs_for_responses: bool, connection_start_surbs: u32, per_request_surbs: u32, @@ -135,16 +140,27 @@ pub(crate) struct Config { impl Config { pub(crate) fn new( + provider_interface_version: ProviderInterfaceVersion, + socks5_protocol_version: Socks5ProtocolVersion, use_surbs_for_responses: bool, connection_start_surbs: u32, per_request_surbs: u32, ) -> Self { Self { + provider_interface_version, + socks5_protocol_version, use_surbs_for_responses, connection_start_surbs, per_request_surbs, } } + + fn request_version(&self) -> RequestVersion { + RequestVersion { + provider_interface: self.provider_interface_version, + provider_protocol: self.socks5_protocol_version, + } + } } /// A client connecting to the Socks proxy server, because @@ -164,7 +180,7 @@ pub(crate) struct SocksClient { self_address: Recipient, started_proxy: bool, lane_queue_lengths: LaneQueueLengths, - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, } impl Drop for SocksClient { @@ -173,7 +189,9 @@ impl Drop for SocksClient { // if we never managed to start a proxy, the entry will not exist in the controller if self.started_proxy { self.controller_sender - .unbounded_send(ControllerCommand::Remove(self.connection_id)) + .unbounded_send(ControllerCommand::Remove { + connection_id: self.connection_id, + }) .unwrap(); } } @@ -190,7 +208,7 @@ impl SocksClient { controller_sender: ControllerSender, self_address: &Recipient, lane_queue_lengths: LaneQueueLengths, - mut shutdown_listener: ShutdownListener, + mut shutdown_listener: TaskClient, ) -> Self { // If this task fails and exits, we don't want to send shutdown signal shutdown_listener.mark_as_success(); @@ -220,7 +238,7 @@ impl SocksClient { } pub async fn send_error(&mut self, err: SocksProxyError) -> Result<(), SocksProxyError> { - let error_text = format!("{}", err); + let error_text = format!("{err}"); let Some(ref version) = self.socks_version else { log::error!("Trying to send error without knowing the version"); return Ok(()); @@ -248,19 +266,26 @@ impl SocksClient { // Send an error back to the client pub async fn send_error_v4(&mut self, r: ResponseCodeV4) -> Result<(), SocksProxyError> { - self.stream.write_all(&[SOCKS4_VERSION, r as u8]).await?; - Ok(()) + self.stream + .write_all(&[SOCKS4_VERSION, r as u8]) + .await + .map_err(|source| SocksProxyError::SocketWriteError { source }) } pub async fn send_error_v5(&mut self, r: ResponseCodeV5) -> Result<(), SocksProxyError> { - self.stream.write_all(&[SOCKS5_VERSION, r as u8]).await?; - Ok(()) + self.stream + .write_all(&[SOCKS5_VERSION, r as u8]) + .await + .map_err(|source| SocksProxyError::SocketWriteError { source }) } /// Shutdown the `TcpStream` to the client and end the session pub async fn shutdown(&mut self) -> Result<(), SocksProxyError> { info!("client is shutting down its TCP stream"); - self.stream.shutdown().await?; + self.stream + .shutdown() + .await + .map_err(|source| SocksProxyError::SocketShutdownFailure { source })?; self.shutdown_listener.mark_as_success(); Ok(()) } @@ -268,11 +293,20 @@ impl SocksClient { /// Initializes the new client, checking that the correct Socks version (5) /// is in use and that the client is authenticated, then runs the request. pub async fn run(&mut self) -> Result<(), SocksProxyError> { - debug!("New connection from: {}", self.stream.peer_addr()?.ip()); + debug!( + "New connection from: {}", + self.stream + .peer_addr() + .map_err(|source| SocksProxyError::PeerAddrExtractionFailure { source })? + .ip() + ); // Read a byte from the stream and determine the version being requested let mut header = [0u8]; - self.stream.read_exact(&mut header).await?; + self.stream + .read_exact(&mut header) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; self.socks_version = match SocksVersion::try_from(header[0]) { Ok(version) => Some(version), @@ -284,7 +318,10 @@ impl SocksClient { if self.socks_version == Some(SocksVersion::V5) { let mut auth = [0u8]; - self.stream.read_exact(&mut auth).await?; + self.stream + .read_exact(&mut auth) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; self.auth_nmethods = auth[0]; self.authenticate_socks5().await?; } @@ -293,8 +330,15 @@ impl SocksClient { } async fn send_anonymous_connect_to_mixnet(&mut self, remote_address: RemoteAddress) { - let req = Request::new_connect(self.connection_id, remote_address, None); - let msg = Message::Request(req); + // TODO: simplify by using `request_version` + let req = Socks5Request::new_connect( + self.config.socks5_protocol_version, + self.connection_id, + remote_address, + None, + ); + let msg = + Socks5ProviderRequest::new_provider_data(self.config.provider_interface_version, req); let input_message = InputMessage::new_anonymous( self.service_provider, @@ -309,8 +353,15 @@ impl SocksClient { } async fn send_connect_to_mixnet_with_return_address(&mut self, remote_address: RemoteAddress) { - let req = Request::new_connect(self.connection_id, remote_address, Some(self.self_address)); - let msg = Message::Request(req); + // TODO: simplify by using `request_version` + let req = Socks5Request::new_connect( + self.config.socks5_protocol_version, + self.connection_id, + remote_address, + Some(self.self_address), + ); + let msg = + Socks5ProviderRequest::new_provider_data(self.config.provider_interface_version, req); let input_message = InputMessage::new_regular( self.service_provider, @@ -350,6 +401,7 @@ impl SocksClient { let input_sender = self.input_sender.clone(); let anonymous = self.config.use_surbs_for_responses; let per_request_surbs = self.config.per_request_surbs; + let request_version = self.config.request_version(); let recipient = self.service_provider; let (stream, _) = ProxyRunner::new( @@ -363,8 +415,16 @@ impl SocksClient { self.shutdown_listener.clone(), ) .run(move |conn_id, read_data, socket_closed| { - let provider_request = Request::new_send(conn_id, read_data, socket_closed); - let provider_message = Message::Request(provider_request); + let provider_request = Socks5Request::new_send( + request_version.provider_protocol, + conn_id, + read_data, + socket_closed, + ); + let provider_message = Socks5ProviderRequest::new_provider_data( + request_version.provider_interface, + provider_request, + ); let lane = TransmissionLane::ConnectionId(conn_id); if anonymous { InputMessage::new_anonymous( @@ -413,7 +473,10 @@ impl SocksClient { self.started_proxy = true; self.controller_sender - .unbounded_send(ControllerCommand::Insert(self.connection_id, mix_sender)) + .unbounded_send(ControllerCommand::Insert { + connection_id: self.connection_id, + connection_sender: mix_sender, + }) .unwrap(); info!( @@ -491,7 +554,13 @@ impl SocksClient { /// into the Authenticator (where it'll be more easily testable) /// would be a good next step. async fn authenticate_socks5(&mut self) -> Result<(), SocksProxyError> { - debug!("Authenticating w/ {}", self.stream.peer_addr()?.ip()); + debug!( + "Authenticating w/ {}", + self.stream + .peer_addr() + .map_err(|source| SocksProxyError::PeerAddrExtractionFailure { source })? + .ip() + ); // Get valid auth methods let methods = self.get_available_methods().await?; trace!("methods: {:?}", methods); @@ -505,27 +574,45 @@ impl SocksClient { response[1] = AuthenticationMethods::UserPass as u8; debug!("Sending USER/PASS packet"); - self.stream.write_all(&response).await?; + self.stream + .write_all(&response) + .await + .map_err(|source| SocksProxyError::SocketWriteError { source })?; let mut header = [0u8; 2]; // Read a byte from the stream and determine the version being requested - self.stream.read_exact(&mut header).await?; + self.stream + .read_exact(&mut header) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; // debug!("Auth Header: [{}, {}]", header[0], header[1]); // Username parsing let ulen = header[1]; let mut username = vec![0; ulen as usize]; - self.stream.read_exact(&mut username).await?; + self.stream + .read_exact(&mut username) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; // Password Parsing - let plen = self.stream.read_u8().await?; + let plen = self + .stream + .read_u8() + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; let mut password = vec![0; plen as usize]; - self.stream.read_exact(&mut password).await?; + self.stream + .read_exact(&mut password) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; - let username_str = String::from_utf8(username)?; - let password_str = String::from_utf8(password)?; + let username_str = String::from_utf8(username) + .map_err(|source| SocksProxyError::MalformedAuthUsername { source })?; + let password_str = String::from_utf8(password) + .map_err(|source| SocksProxyError::MalformedAuthPassword { source })?; let user = User { username: username_str, @@ -536,11 +623,17 @@ impl SocksClient { if self.authenticator.is_allowed(&user) { debug!("Access Granted. User: {}", user.username); let response = [1, ResponseCodeV5::Success as u8]; - self.stream.write_all(&response).await?; + self.stream + .write_all(&response) + .await + .map_err(|source| SocksProxyError::SocketWriteError { source })?; } else { debug!("Access Denied. User: {}", user.username); let response = [1, ResponseCodeV5::Failure as u8]; - self.stream.write_all(&response).await?; + self.stream + .write_all(&response) + .await + .map_err(|source| SocksProxyError::SocketWriteError { source })?; // Shutdown self.shutdown().await?; @@ -551,12 +644,18 @@ impl SocksClient { // set the default auth method (no auth) response[1] = AuthenticationMethods::NoAuth as u8; debug!("Sending NOAUTH packet"); - self.stream.write_all(&response).await?; + self.stream + .write_all(&response) + .await + .map_err(|source| SocksProxyError::SocketWriteError { source })?; Ok(()) } else { warn!("Client has no suitable authentication methods!"); response[1] = AuthenticationMethods::NoMethods as u8; - self.stream.write_all(&response).await?; + self.stream + .write_all(&response) + .await + .map_err(|source| SocksProxyError::SocketWriteError { source })?; self.shutdown().await?; Err(ResponseCodeV5::Failure.into()) } @@ -567,7 +666,10 @@ impl SocksClient { let mut methods: Vec = Vec::with_capacity(self.auth_nmethods as usize); for _ in 0..self.auth_nmethods { let mut method = [0u8; 1]; - self.stream.read_exact(&mut method).await?; + self.stream + .read_exact(&mut method) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; if self.authenticator.auth_methods.contains(&method[0]) { methods.append(&mut method.to_vec()); } diff --git a/clients/socks5/src/socks/mixnet_responses.rs b/clients/socks5/src/socks/mixnet_responses.rs index 7613a32019..ed45958381 100644 --- a/clients/socks5/src/socks/mixnet_responses.rs +++ b/clients/socks5/src/socks/mixnet_responses.rs @@ -1,21 +1,22 @@ -use std::time::Duration; - use futures::channel::mpsc; use futures::StreamExt; use log::*; use client_core::client::received_buffer::ReconstructedMessagesReceiver; use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender}; -use nymsphinx::receiver::ReconstructedMessage; -use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender}; -use socks5_requests::Message; -use task::ShutdownListener; +use nym_service_providers_common::interface::{ControlResponse, ResponseContent}; +use nym_socks5_proxy_helpers::connection_controller::ControllerSender; +use nym_socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent}; +use nym_sphinx::receiver::ReconstructedMessage; +use nym_task::TaskClient; + +use crate::error::Socks5ClientError; pub(crate) struct MixnetResponseListener { buffer_requester: ReceivedBufferRequestSender, mix_response_receiver: ReconstructedMessagesReceiver, controller_sender: ControllerSender, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl Drop for MixnetResponseListener { @@ -25,9 +26,9 @@ impl Drop for MixnetResponseListener { .unbounded_send(ReceivedBufferMessage::ReceiverDisconnect) { if self.shutdown.is_shutdown_poll() { - log::debug!("The buffer request failed: {}", err); + log::debug!("The buffer request failed: {err}"); } else { - log::error!("The buffer request failed: {}", err); + log::error!("The buffer request failed: {err}"); } } } @@ -37,7 +38,7 @@ impl MixnetResponseListener { pub(crate) fn new( buffer_requester: ReceivedBufferRequestSender, controller_sender: ControllerSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { let (mix_response_sender, mix_response_receiver) = mpsc::unbounded(); buffer_requester @@ -52,50 +53,82 @@ impl MixnetResponseListener { } } - async fn on_message(&self, reconstructed_message: ReconstructedMessage) { + fn on_control_response( + &self, + control_response: ControlResponse, + ) -> Result<(), Socks5ClientError> { + error!("received a control response which we don't know how to handle yet!"); + error!("got: {:?}", control_response); + + // I guess we'd need another channel here to forward those to where they need to go + + Ok(()) + } + + fn on_provider_data_response( + &self, + provider_response: Socks5Response, + ) -> Result<(), Socks5ClientError> { + match provider_response.content { + Socks5ResponseContent::ConnectionError(err_response) => { + error!( + "Network requester failed on connection id {} with error: {}", + err_response.connection_id, err_response.network_requester_error + ); + Err(err_response.into()) + } + Socks5ResponseContent::NetworkData(response) => { + self.controller_sender + .unbounded_send(response.into()) + .unwrap(); + Ok(()) + } + } + } + + fn on_message( + &self, + reconstructed_message: ReconstructedMessage, + ) -> Result<(), Socks5ClientError> { let raw_message = reconstructed_message.message; if reconstructed_message.sender_tag.is_some() { warn!("this message was sent anonymously - it couldn't have come from the service provider"); } - - let response = match Message::try_from_bytes(&raw_message) { + match Socks5ProviderResponse::try_from_bytes(&raw_message) { Err(err) => { - warn!("failed to parse received response - {:?}", err); - return; + warn!("failed to parse received response: {err}"); + Ok(()) } - Ok(Message::Request(_)) => { - warn!("unexpected request"); - return; - } - Ok(Message::Response(data)) => data, - Ok(Message::NetworkRequesterResponse(r)) => { - error!( - "Network requester failed on connection id {} with error: {}", - r.connection_id, r.network_requester_error + Ok(response) => { + // as long as the client used the same (or older) interface than the service provider, + // the response should have used exactly the same version + trace!( + "the received response was sent with {:?} interface version", + response.interface_version ); - return; + match response.content { + ResponseContent::Control(control_response) => { + self.on_control_response(control_response) + } + ResponseContent::ProviderData(provider_response) => { + self.on_provider_data_response(provider_response) + } + } } - }; - - self.controller_sender - .unbounded_send(ControllerCommand::Send( - response.connection_id, - response.data, - response.is_closed, - )) - .unwrap(); + } } pub(crate) async fn run(&mut self) { while !self.shutdown.is_shutdown() { tokio::select! { - received_responses = self.mix_response_receiver.next() => match received_responses { - Some(received_responses) => { + received_responses = self.mix_response_receiver.next() => { + if let Some(received_responses) = received_responses { for reconstructed_message in received_responses { - self.on_message(reconstructed_message).await; + if let Err(err) = self.on_message(reconstructed_message) { + self.shutdown.send_status_msg(Box::new(err)); + } } - }, - None => { + } else { log::trace!("MixnetResponseListener: Stopping since channel closed"); break; } @@ -105,10 +138,7 @@ impl MixnetResponseListener { } } } - #[cfg(not(target_arch = "wasm32"))] - tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv()) - .await - .expect("Task stopped without shutdown called"); + self.shutdown.recv_timeout().await; log::debug!("MixnetResponseListener: Exiting"); } } diff --git a/clients/socks5/src/socks/mod.rs b/clients/socks5/src/socks/mod.rs index 16603839ff..36cc580480 100644 --- a/clients/socks5/src/socks/mod.rs +++ b/clients/socks5/src/socks/mod.rs @@ -33,7 +33,7 @@ impl TryFrom for SocksVersion { match version { SOCKS4_VERSION => Ok(Self::V4), SOCKS5_VERSION => Ok(Self::V5), - _ => Err(SocksProxyError::UnsupportedProxyVersion(version)), + _ => Err(SocksProxyError::UnsupportedProxyVersion { version }), } } } diff --git a/clients/socks5/src/socks/request.rs b/clients/socks5/src/socks/request.rs index ecf9b656f3..782273f8b7 100644 --- a/clients/socks5/src/socks/request.rs +++ b/clients/socks5/src/socks/request.rs @@ -28,7 +28,10 @@ impl SocksRequest { log::trace!("read from stream socks4"); let mut packet = [0u8; 3]; - stream.read_exact(&mut packet).await?; + stream + .read_exact(&mut packet) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; // CD (command) let Some(command) = SocksCommand::from(packet[0] as usize) else { @@ -43,7 +46,10 @@ impl SocksRequest { // DSTIP let mut ip = [0u8; 4]; - stream.read_exact(&mut ip).await?; + stream + .read_exact(&mut ip) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; // USERID let _userid = read_until_zero(stream).await; @@ -76,12 +82,17 @@ impl SocksRequest { let mut packet = [0u8; 4]; // Read a byte from the stream and determine the version being requested - stream.read_exact(&mut packet).await?; + stream + .read_exact(&mut packet) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; // VER if packet[0] != SOCKS5_VERSION { warn!("Unsupported version: SOCKS{}", packet[0]); - return Err(SocksProxyError::UnsupportedProxyVersion(packet[0])); + return Err(SocksProxyError::UnsupportedProxyVersion { + version: (packet[0]), + }); } // CMD @@ -103,26 +114,41 @@ impl SocksRequest { let addr = match addr_type { AddrType::Domain => { let mut domain_length = [0u8]; - stream.read_exact(&mut domain_length).await?; + stream + .read_exact(&mut domain_length) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; let mut domain = vec![0u8; domain_length[0] as usize]; - stream.read_exact(&mut domain).await?; + stream + .read_exact(&mut domain) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; domain } AddrType::V4 => { let mut addr = [0u8; 4]; - stream.read_exact(&mut addr).await?; + stream + .read_exact(&mut addr) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; addr.to_vec() } AddrType::V6 => { let mut addr = [0u8; 16]; - stream.read_exact(&mut addr).await?; + stream + .read_exact(&mut addr) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; addr.to_vec() } }; // DST.PORT let mut port = [0u8; 2]; - stream.read_exact(&mut port).await?; + stream + .read_exact(&mut port) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; let port = merge_u8_into_u16(port[0], port[1]); Ok(SocksRequest { @@ -179,7 +205,10 @@ where let mut result = Vec::new(); let mut char = [0u8]; loop { - stream.read_exact(&mut char).await?; + stream + .read_exact(&mut char) + .await + .map_err(|source| SocksProxyError::SocketReadError { source })?; if char[0] == 0 { break; } diff --git a/clients/socks5/src/socks/server.rs b/clients/socks5/src/socks/server.rs index 0bba59ee0b..57bc8fe25e 100644 --- a/clients/socks5/src/socks/server.rs +++ b/clients/socks5/src/socks/server.rs @@ -4,16 +4,16 @@ use super::{ authentication::Authenticator, client::SocksClient, mixnet_responses::MixnetResponseListener, }; use crate::socks::client; -use client_connections::{ConnectionCommandSender, LaneQueueLengths}; use client_core::client::{ inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender, }; use log::*; -use nymsphinx::addressing::clients::Recipient; -use proxy_helpers::connection_controller::{BroadcastActiveConnections, Controller}; +use nym_socks5_proxy_helpers::connection_controller::Controller; +use nym_sphinx::addressing::clients::Recipient; +use nym_task::connections::{ConnectionCommandSender, LaneQueueLengths}; +use nym_task::TaskClient; use std::net::SocketAddr; use tap::TapFallible; -use task::ShutdownListener; use tokio::net::TcpListener; /// A Socks5 server that listens for connections. @@ -24,7 +24,7 @@ pub struct SphinxSocksServer { self_address: Recipient, client_config: client::Config, lane_queue_lengths: LaneQueueLengths, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl SphinxSocksServer { @@ -36,7 +36,7 @@ impl SphinxSocksServer { self_address: Recipient, lane_queue_lengths: LaneQueueLengths, client_config: client::Config, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { // hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can // just modify the config @@ -69,7 +69,7 @@ impl SphinxSocksServer { // controller for managing all active connections let (mut active_streams_controller, controller_sender) = Controller::new( client_connection_tx, - BroadcastActiveConnections::Off, + //BroadcastActiveConnections::Off, self.shutdown.clone(), ); tokio::spawn(async move { @@ -86,6 +86,11 @@ impl SphinxSocksServer { mixnet_response_listener.run().await; }); + // TODO:, if required, there should be another task here responsible for control requests. + // it should get `input_sender` to send actual requests into the mixnet + // and some channel that connects it from `MixnetResponseListener` to receive + // any control responses + loop { tokio::select! { Ok((stream, _remote)) = listener.accept() => { @@ -103,7 +108,7 @@ impl SphinxSocksServer { tokio::spawn(async move { if let Err(err) = client.run().await { - error!("Error! {}", err); + error!("Error! {err}"); if client.send_error(err).await.is_err() { warn!("Failed to send error code"); }; diff --git a/clients/socks5/src/socks/types.rs b/clients/socks5/src/socks/types.rs index 669a6b1093..964377bdc6 100644 --- a/clients/socks5/src/socks/types.rs +++ b/clients/socks5/src/socks/types.rs @@ -1,3 +1,7 @@ +use nym_socks5_requests::Socks5RequestError; +use std::string::FromUtf8Error; +use thiserror::Error; + /// SOCKS4 Response codes #[allow(dead_code)] pub(crate) enum ResponseCodeV4 { @@ -8,9 +12,8 @@ pub(crate) enum ResponseCodeV4 { } /// Possible SOCKS5 Response Codes -#[allow(dead_code)] -#[derive(Debug, thiserror::Error)] -pub(crate) enum ResponseCodeV5 { +#[derive(Debug, Error)] +pub enum ResponseCodeV5 { #[error("SOCKS5 Server Success")] Success = 0x00, #[error("SOCKS5 Server Failure")] @@ -31,30 +34,55 @@ pub(crate) enum ResponseCodeV5 { AddrTypeNotSupported = 0x08, } -#[derive(Debug)] +#[derive(Error, Debug)] pub enum SocksProxyError { - GenericError(Box), - UnsupportedProxyVersion(u8), -} + #[error("{version} of the socks protocol is not supported by this client")] + UnsupportedProxyVersion { version: u8 }, -impl std::fmt::Display for SocksProxyError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SocksProxyError::GenericError(err) => write!(f, "GenericError - {}", err), - SocksProxyError::UnsupportedProxyVersion(version) => { - write!(f, "Unsupported proxy version {}", version) - } - } - } -} + #[error("failed to write to the socket: {source}")] + SocketWriteError { + #[source] + source: std::io::Error, + }, -impl From for SocksProxyError -where - E: std::error::Error + Send + Sync + 'static, -{ - fn from(err: E) -> Self { - SocksProxyError::GenericError(Box::new(err)) - } + #[error("failed to read from the socket: {source}")] + SocketReadError { + #[source] + source: std::io::Error, + }, + + #[error("failed to shutdown underlying socket stream: {source}")] + SocketShutdownFailure { + #[source] + source: std::io::Error, + }, + + #[error("failed to extract ip address of the connected peer: {source}")] + PeerAddrExtractionFailure { + #[source] + source: std::io::Error, + }, + + #[error("failed to authenticate user due to malformed username: {source}")] + MalformedAuthUsername { + #[source] + source: FromUtf8Error, + }, + + #[error("failed to authenticate user due to malformed password: {source}")] + MalformedAuthPassword { + #[source] + source: FromUtf8Error, + }, + + #[error(transparent)] + Socks5ResponseFailure(#[from] ResponseCodeV5), + + #[error("could not complete the provider request: {source}")] + ProviderRequestFailure { + #[from] + source: Socks5RequestError, + }, } /// DST.addr variant types diff --git a/clients/validator/.eslintrc.json b/clients/validator/.eslintrc.json index 985f191ff5..1069570078 100644 --- a/clients/validator/.eslintrc.json +++ b/clients/validator/.eslintrc.json @@ -1,83 +1,71 @@ { - "root": true, - "env": { - "browser": true, - "es6": true, - "node": true - }, - "parserOptions": { - "ecmaVersion": 2019, - "sourceType": "module" - }, - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "plugins": ["prettier", "mocha"], - "extends": [ - "airbnb-base", - "airbnb-typescript/base", - "prettier"], - "rules": { - "prettier/prettier": "error", - "import/prefer-default-export": "off", - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/*.test.[jt]s", - "**/*.spec.[jt]s" - ] - } - ], - "import/extensions": [ - "error", - "ignorePackages", - { - "ts": "never", - "js": "never" - } - ] - }, - "overrides": [ - { - "files": "**/*.ts", - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "./tsconfig.json" - }, - "plugins": ["@typescript-eslint/eslint-plugin"], - "extends": [ - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "prettier" - ], - "rules": { - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-var-requires": "off", - "no-use-before-define": [0], - "@typescript-eslint/no-use-before-define": [1], - "import/no-unresolved": 0, - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": [ - "**/*.test.ts", - "**/*.spec.ts" - ] - } - ], - "quotes": "off", - "@typescript-eslint/quotes": [ - 2, - "single", - { - "avoidEscape": true - } - ], - "@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }] - } - } + "root": true, + "env": { + "browser": true, + "es6": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 2019, + "sourceType": "module" + }, + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "plugins": ["prettier", "mocha"], + "extends": ["airbnb-base", "airbnb-typescript/base", "prettier"], + "rules": { + "prettier/prettier": "error", + "import/prefer-default-export": "off", + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": ["**/*.test.[jt]s", "**/*.spec.[jt]s"] + } + ], + "import/extensions": [ + "error", + "ignorePackages", + { + "ts": "never", + "js": "never" + } ] + }, + "overrides": [ + { + "files": "**/*.ts", + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json" + }, + "plugins": ["@typescript-eslint/eslint-plugin"], + "extends": ["plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "prettier"], + "rules": { + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-var-requires": "off", + "no-use-before-define": [0], + "@typescript-eslint/no-use-before-define": [1], + "import/no-unresolved": 0, + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": ["**/*.test.ts", "**/*.spec.ts"] + } + ], + "quotes": "off", + "@typescript-eslint/quotes": [ + 2, + "single", + { + "avoidEscape": true + } + ], + "@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }] + } + } + ], + "ignorePatterns": ["tsconfig.json", "*.d.ts", "dist/**/*", "dist", "node_modules"] } diff --git a/clients/validator/.gitignore b/clients/validator/.gitignore index 5a4946114d..ee82866c85 100644 --- a/clients/validator/.gitignore +++ b/clients/validator/.gitignore @@ -4,4 +4,5 @@ coverage dist docs examples/accounts -node_modules \ No newline at end of file +node_modules +.env \ No newline at end of file diff --git a/clients/validator/.npmignore b/clients/validator/.npmignore index 62ab11e57c..298aabc724 100644 --- a/clients/validator/.npmignore +++ b/clients/validator/.npmignore @@ -1,3 +1,5 @@ coverage node_modules -tests \ No newline at end of file +tests +src +type diff --git a/clients/validator/README.md b/clients/validator/README.md index d54ebe7c77..41c1ae73af 100644 --- a/clients/validator/README.md +++ b/clients/validator/README.md @@ -1,40 +1,39 @@ -Nym Validator Client -==================== +# Nym Validator Client (Typescript) -A TypeScript client for interacting with CosmWasm smart contracts in Nym validators. +A TypeScript client for interacting with CosmWasm smart contracts in Nym validators. -Running examples ------------------ - -With the code checked out, `cd examples`. This folder contains runnable example code that will set up a blockchain and allow you to interact with it through the client. - -Running tests -------------- +Include the Nym Validator in your project: ``` -npm test +yarn add @nymproject/nym-validator-client ``` -You can also trigger test execution with a test watcher. I don't have the centuries of life left to me that are needed to fight through the arcana of wiring up a working TypeScript mocha triggered execution setup, so for now my Cargo-based hack is: - +Connect to validator and make queries ``` -cargo watch -s "cd clients/validator && npm test" +import Validator from '@nymproject/nym-validator-client' + +const main = async () => { + + const client = await Validator.connectForQuery(rpcAddress, validatorAddress, prefix, mixnetContractAddress, vestingContractAddress, denom) + + client.getBalance(address) + +} + ``` -It's ugly but works fine if you have Cargo installed. TypeScript setup help happily accepted here. +Connect to validator for performing actions -Generating Documentation ------------------------- +``` +import Validator from '@nymproject/nym-validator-client' -You can generate docs by running `npm run docs`. Generated output will appear in the `docs` directory. +const main = async () => { -Packaging ------------------------- + const client = await Validator.connect(mnemonic, rpcAddress, validatorAddress, prefix, mixnetContractAddress, vestingContractAddress, denom) -If you're a Nym platform developer who's made changes to the client and wants to re-publish the package to NPM, here's how you do it: + const res = await client.send(address, [{ amount: '10000000', denom: 'unym' }]); -1. Bump the version number (use SemVer) -1. `npm run build` -1. `npm login` (if you haven't already) -1. `npm publish` \ No newline at end of file +} + +``` diff --git a/clients/validator/package.json b/clients/validator/package.json index 6179e11b19..47766a842b 100644 --- a/clients/validator/package.json +++ b/clients/validator/package.json @@ -4,15 +4,23 @@ "description": "A TypeScript client for interacting with smart contracts in Nym validators", "repository": "https://github.com/nymtech/nym", "main": "./dist/index.js", - "types": "dist/index.d.ts", + "types": "./dist/index.d.ts", "scripts": { - "build": "tsc", - "test": "ts-mocha tests/**/*.test.ts", + "build": "rollup -c ./rollup.config.mjs", + "build:types": "rollup-type-bundler --dist ./dist/nym-validator-client", + "build:prod": "sh ./scripts/build-prod.sh", + "test": "ts-mocha -p ./tsconfig.test.json ./src/tests/**/*.test.ts", + "testmock": "ts-mocha -p ./tsconfig.test.json ./src/tests/mock/*.test.ts", "coverage": "nyc npm test", - "lint": "eslint src", - "lint:fix": "eslint src --fix", + "clean": "rm -rf ./dist", + "lint": "eslint", + "lint:fix": "eslint --fix", + "lint:tsc": "tsc --noEmit", "docs": "typedoc --out docs src/index.ts" }, + "files": [ + "./dist/*" + ], "keywords": [], "author": "Nym Technologies SA (https://nymtech.net)", "contributors": [ @@ -21,6 +29,14 @@ ], "license": "Apache-2.0", "devDependencies": { + "@favware/rollup-type-bundler": "^2.0.0", + "@nymproject/types": "^1.0.0", + "@rollup/plugin-commonjs": "^24.0.1", + "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-typescript": "^11.0.0", + "@rollup/plugin-node-resolve": "^15.0.1", + "rollup": "^3.17.2", + "rollup-plugin-dts": "^5.2.0", "@typescript-eslint/eslint-plugin": "^5.7.0", "@typescript-eslint/parser": "^5.7.0", "eslint": "^7.18.0", @@ -31,21 +47,21 @@ "eslint-plugin-import": "^2.25.4", "eslint-plugin-mocha": "^10.0.3", "eslint-plugin-prettier": "^4.0.0", - "expect": "^28.1.3", "mocha": "^10.0.0", "prettier": "^2.5.1", - "typedoc": "^0.22.13", "ts-mocha": "^10.0.0", - "typescript": "^4.6.2" - }, - "dependencies": { - "@cosmjs/cosmwasm-stargate": "^0.28.0", - "@cosmjs/crypto": "^0.28.0", - "@cosmjs/math": "^0.28.0", - "@cosmjs/proto-signing": "^0.28.0", - "@cosmjs/stargate": "^0.28.0", - "@cosmjs/tendermint-rpc": "^0.28.0", - "axios": "^0.26.1", - "cosmjs-types": "^0.4.1" + "typedoc": "^0.22.13", + "typescript": "^4.6.2", + "cosmjs-types": "^0.4.1", + "dotenv": "^16.0.3", + "expect": "^28.1.3", + "moq.ts": "^7.3.4", + "@cosmjs/cosmwasm-stargate": "^0.29.5", + "@cosmjs/crypto": "^0.29.5", + "@cosmjs/math": "^0.29.5", + "@cosmjs/proto-signing": "^0.29.5", + "@cosmjs/stargate": "^0.29.5", + "@cosmjs/tendermint-rpc": "^0.29.5", + "axios": "^1.3.3" } } diff --git a/clients/validator/rollup.config.mjs b/clients/validator/rollup.config.mjs new file mode 100644 index 0000000000..f78cf449cf --- /dev/null +++ b/clients/validator/rollup.config.mjs @@ -0,0 +1,15 @@ +import typescript from '@rollup/plugin-typescript'; +import resolve from '@rollup/plugin-node-resolve'; +import json from '@rollup/plugin-json'; +import commonjs from '@rollup/plugin-commonjs'; + +export default [ + { + input: './src/index.ts', + output: { + dir: 'dist/nym-validator-client', + format: 'cjs', + }, + plugins: [resolve(), typescript(), commonjs(), json()], + }, +]; diff --git a/clients/validator/scripts/build-prod.sh b/clients/validator/scripts/build-prod.sh new file mode 100644 index 0000000000..06188652b0 --- /dev/null +++ b/clients/validator/scripts/build-prod.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset + +rm -rf ./dist || true +rm -rf ../../dist || true + + +# Bundle application + +yarn build + +# Bundle types + +yarn build:types + +# Build package.json for bundle + +node ./scripts/buildPackageJson.mjs + +# Copy README + +cp README.md dist/nym-validator-client + +# move the output outside of the yarn/npm workspaces + +mv ./dist ../../ + +echo "Output can be found in:" +realpath ../../dist \ No newline at end of file diff --git a/clients/validator/scripts/buildPackageJson.mjs b/clients/validator/scripts/buildPackageJson.mjs new file mode 100644 index 0000000000..da95f10e9d --- /dev/null +++ b/clients/validator/scripts/buildPackageJson.mjs @@ -0,0 +1,20 @@ +import * as fs from 'fs'; + +// parse the package.json from the SDK, so we can keep fields like the name and version +const json = JSON.parse(fs.readFileSync('./package.json').toString()); + +// defaults (NB: these are in the output file locations) +const main = 'index.js'; +const types = 'index.d.ts'; + +// make a package.json for the bundle +const packageJson = { + name: json.name, + version: json.version, + license: json.license, + author: json.author, + main, + types, +}; + +fs.writeFileSync('./dist/nym-validator-client/package.json', JSON.stringify(packageJson, null, 2)); diff --git a/clients/validator/src/currency.ts b/clients/validator/src/currency.ts index 483d13b408..216905f8b1 100644 --- a/clients/validator/src/currency.ts +++ b/clients/validator/src/currency.ts @@ -1,5 +1,6 @@ import { Decimal } from '@cosmjs/math'; import { Coin } from '@cosmjs/stargate'; +import { CoinMap } from './types/shared'; // NARROW NO-BREAK SPACE (U+202F) const thinSpace = '\u202F'; @@ -34,15 +35,6 @@ export function nativeToPrintable(nativeValue: string): string { return Decimal.fromAtomics(nativeValue, 6).toString(); } -export interface MappedCoin { - readonly denom: string; - readonly fractionalDigits: number; -} - -export interface CoinMap { - readonly [key: string]: MappedCoin; -} - export function nativeCoinToDisplay(coin: Coin, coinMap: CoinMap): Coin { if (!coinMap) return coin; diff --git a/clients/validator/src/index.ts b/clients/validator/src/index.ts index 642fc2b077..e11d24525f 100644 --- a/clients/validator/src/index.ts +++ b/clients/validator/src/index.ts @@ -1,6 +1,3 @@ -import { Bip39, Random } from '@cosmjs/crypto'; -import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing'; -import { coin as cosmosCoin, Coin, DeliverTxResponse, isDeliverTxFailure, StdFee } from '@cosmjs/stargate'; import { ExecuteResult, InstantiateOptions, @@ -8,45 +5,37 @@ import { MigrateResult, UploadResult, } from '@cosmjs/cosmwasm-stargate'; -import SigningClient, { ISigningClient } from './signing-client'; +import { Bip39, Random } from '@cosmjs/crypto'; +import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing'; +import { Coin, coin as cosmosCoin, DeliverTxResponse, isDeliverTxFailure, StdFee } from '@cosmjs/stargate'; import { ContractStateParams, Delegation, Gateway, GatewayBond, + GatewayOwnershipResponse, + LayerDistribution, MixnetContractVersion, MixNode, MixNodeBond, + MixNodeCostParams, + MixNodeDetails, + MixNodeRewarding, + MixOwnershipResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse, - PagedMixnodeResponse, -} from './types'; -import { - CoinMap, - displayAmountToNative, - MappedCoin, - nativeCoinToDisplay, - nativeToPrintable, - printableBalance, - printableCoin, -} from './currency'; + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + PagedUnbondedMixnodesResponse, + RewardingParams, + StakeSaturationResponse, + UnbondedMixnodeResponse, +} from '@nymproject/types'; import QueryClient from './query-client'; -import { nymGasPrice } from './stargate-helper'; - -export { coins, coin } from '@cosmjs/stargate'; -export { Coin }; -export { - displayAmountToNative, - nativeCoinToDisplay, - printableCoin, - printableBalance, - nativeToPrintable, - MappedCoin, - CoinMap, -}; -export { nymGasPrice }; +import SigningClient, { ISigningClient } from './signing-client'; +import { ContractState } from './types/shared'; export interface INymClient { readonly mixnetContract: string; @@ -85,8 +74,8 @@ export default class ValidatorClient implements INymClient { static async connect( mnemonic: string, - nymdUrl: string, - validatorApiUrl: string, + nyxdUrl: string, + nymApiUrl: string, prefix: string, mixnetContract: string, vestingContract: string, @@ -94,19 +83,19 @@ export default class ValidatorClient implements INymClient { ): Promise { const wallet = await ValidatorClient.buildWallet(mnemonic, prefix); - const signingClient = await SigningClient.connectWithNymSigner(wallet, nymdUrl, validatorApiUrl, prefix, denom); + const signingClient = await SigningClient.connectWithNymSigner(wallet, nyxdUrl, nymApiUrl, prefix, denom); return new ValidatorClient(signingClient, prefix, mixnetContract, vestingContract, denom); } static async connectForQuery( - nymdUrl: string, - validatorApiUrl: string, + nyxdUrl: string, + nymApiUrl: string, prefix: string, mixnetContract: string, vestingContract: string, denom: string, ): Promise { - const queryClient = await QueryClient.connectWithNym(nymdUrl, validatorApiUrl); + const queryClient = await QueryClient.connectWithNym(nyxdUrl, nymApiUrl); return new ValidatorClient(queryClient, prefix, mixnetContract, vestingContract, denom); } @@ -147,7 +136,7 @@ export default class ValidatorClient implements INymClient { return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, signerOptions); } - getBalance(address: string): Promise { + async getBalance(address: string): Promise { return this.client.getBalance(address, this.denom); } @@ -159,15 +148,39 @@ export default class ValidatorClient implements INymClient { return this.client.getCachedMixnodes(); } - async getActiveMixnodes(): Promise { + async getStakeSaturation(mixId: number): Promise { + return this.client.getStakeSaturation(this.mixnetContract, mixId); + } + + async getActiveMixnodes(): Promise { return this.client.getActiveMixnodes(); } + async getUnbondedMixNodeInformation(mixId: number): Promise { + return this.client.getUnbondedMixNodeInformation(this.mixnetContract, mixId); + } + async getRewardedMixnodes(): Promise { return this.client.getRewardedMixnodes(); } - public async getMixnetContractSettings(): Promise { + async getMixnodeRewardingDetails(mixId: number): Promise { + return this.client.getMixnodeRewardingDetails(this.mixnetContract, mixId); + } + + async getOwnedMixnode(address: string): Promise { + return this.client.getOwnedMixnode(this.mixnetContract, address); + } + + async ownsGateway(address: string): Promise { + return this.client.ownsGateway(this.mixnetContract, address); + } + + async getLayerDistribution(): Promise { + return this.client.getLayerDistribution(this.mixnetContract); + } + + public async getMixnetContractSettings(): Promise { return this.client.getStateParams(this.mixnetContract); } @@ -175,29 +188,30 @@ export default class ValidatorClient implements INymClient { return this.client.getContractVersion(this.mixnetContract); } - public async getRewardPool(): Promise { - return this.client.getRewardPool(this.mixnetContract); + public async getVestingContractVersion(): Promise { + return this.client.getContractVersion(this.vestingContract); } - public async getCirculatingSupply(): Promise { - return this.client.getCirculatingSupply(this.mixnetContract); + public async getSpendableCoins(vestingAccountAddress: string): Promise { + return this.client.getSpendableCoins(this.vestingContract, vestingAccountAddress); } - public async getSybilResistancePercent(): Promise { - return this.client.getSybilResistancePercent(this.mixnetContract); + public async getRewardParams(): Promise { + return this.client.getRewardParams(this.mixnetContract); } - public async getIntervalRewardPercent(): Promise { - return this.client.getIntervalRewardPercent(this.mixnetContract); - } - - public async getAllNymdMixnodes(): Promise { - let mixNodes: MixNodeBond[] = []; + async getUnbondedMixNodes(): Promise { + let mixNodes: UnbondedMixnodeResponse[] = []; const limit = 50; let startAfter; for (;;) { // eslint-disable-next-line no-await-in-loop - const pagedResponse: PagedMixnodeResponse = await this.client.getMixNodesPaged(this.mixnetContract, limit); + const pagedResponse: PagedUnbondedMixnodesResponse = await this.client.getUnbondedMixNodes( + this.mixnetContract, + limit, + startAfter, + ); + mixNodes = mixNodes.concat(pagedResponse.nodes); startAfter = pagedResponse.start_next_after; // if `start_next_after` is not set, we're done @@ -209,14 +223,18 @@ export default class ValidatorClient implements INymClient { return mixNodes; } - public async getAllNymdGateways(): Promise { - let gateways: GatewayBond[] = []; + public async getMixNodeBonds(): Promise { + let mixNodes: MixNodeBond[] = []; const limit = 50; let startAfter; for (;;) { // eslint-disable-next-line no-await-in-loop - const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract, limit); - gateways = gateways.concat(pagedResponse.nodes); + const pagedResponse: PagedMixNodeBondResponse = await this.client.getMixNodeBonds( + this.mixnetContract, + limit, + startAfter, + ); + mixNodes = mixNodes.concat(pagedResponse.nodes); startAfter = pagedResponse.start_next_after; // if `start_next_after` is not set, we're done if (!startAfter) { @@ -224,15 +242,42 @@ export default class ValidatorClient implements INymClient { } } - return gateways; + return mixNodes; + } + + public async getMixNodesDetailed(): Promise { + let mixNodes: MixNodeDetails[] = []; + const limit = 50; + let startAfter; + for (;;) { + // eslint-disable-next-line no-await-in-loop + const pagedResponse: PagedMixNodeDetailsResponse = await this.client.getMixNodesDetailed( + this.mixnetContract, + limit, + startAfter, + ); + mixNodes = mixNodes.concat(pagedResponse.nodes); + startAfter = pagedResponse.start_next_after; + // if `start_next_after` is not set, we're done + if (!startAfter) { + break; + } + } + + return mixNodes; + } + + public async getAllNyxdGateways(): Promise { + const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract); + return pagedResponse.nodes; } /** * Gets list of all delegations towards particular mixnode. * - * @param mixIdentity identity of the node to which the delegation was sent + * @param mix_id identity of the node to which the delegation was sent */ - public async getAllNymdSingleMixnodeDelegations(mixIdentity: string): Promise { + public async getAllNyxdSingleMixnodeDelegations(mix_id: number): Promise { let delegations: Delegation[] = []; const limit = 250; let startAfter; @@ -240,7 +285,7 @@ export default class ValidatorClient implements INymClient { // eslint-disable-next-line no-await-in-loop const pagedResponse: PagedMixDelegationsResponse = await this.client.getMixNodeDelegationsPaged( this.mixnetContract, - mixIdentity, + mix_id, limit, startAfter, ); @@ -255,7 +300,7 @@ export default class ValidatorClient implements INymClient { return delegations; } - public async getAllNymdDelegatorDelegations(delegator: string): Promise { + public async getAllNyxdDelegatorDelegations(delegator: string): Promise { let delegations: Delegation[] = []; const limit = 250; let startAfter; @@ -278,13 +323,13 @@ export default class ValidatorClient implements INymClient { return delegations; } - public async getAllNymdNetworkDelegations(): Promise { + public async getAllNyxdDelegations(): Promise { let delegations: Delegation[] = []; const limit = 250; let startAfter; for (;;) { // eslint-disable-next-line no-await-in-loop - const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllNetworkDelegationsPaged( + const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllDelegationsPaged( this.mixnetContract, limit, startAfter, @@ -300,6 +345,10 @@ export default class ValidatorClient implements INymClient { return delegations; } + public async getDelegationDetails(mix_id: number, delegator: string): Promise { + return this.client.getDelegationDetails(this.mixnetContract, mix_id, delegator); + } + /** * Generate a minimum gateway bond required to create a fresh mixnode. * @@ -308,7 +357,7 @@ export default class ValidatorClient implements INymClient { public async minimumMixnodePledge(): Promise { const stateParams = await this.getMixnetContractSettings(); // we trust the contract to return a valid number - return cosmosCoin(stateParams.minimum_mixnode_pledge, this.prefix); + return cosmosCoin(stateParams.params.minimum_mixnode_pledge, this.prefix); } /** @@ -319,7 +368,7 @@ export default class ValidatorClient implements INymClient { public async minimumGatewayPledge(): Promise { const stateParams = await this.getMixnetContractSettings(); // we trust the contract to return a valid number - return cosmosCoin(stateParams.minimum_gateway_pledge, this.prefix); + return cosmosCoin(stateParams.params.minimum_gateway_pledge, this.prefix); } public async send( @@ -393,12 +442,21 @@ export default class ValidatorClient implements INymClient { public async bondMixNode( mixNode: MixNode, ownerSignature: string, + costParams: MixNodeCostParams, pledge: Coin, fee?: StdFee | 'auto' | number, memo?: string, ): Promise { this.assertSigning(); - return (this.client as ISigningClient).bondMixNode(this.mixnetContract, mixNode, ownerSignature, pledge, fee, memo); + return (this.client as ISigningClient).bondMixNode( + this.mixnetContract, + mixNode, + costParams, + ownerSignature, + pledge, + fee, + memo, + ); } public async unbondMixNode(fee?: StdFee | 'auto' | number, memo?: string): Promise { @@ -423,29 +481,29 @@ export default class ValidatorClient implements INymClient { } public async delegateToMixNode( - mixIdentity: string, + mixId: number, amount: Coin, fee?: StdFee | 'auto' | number, memo?: string, ): Promise { this.assertSigning(); - return (this.client as ISigningClient).delegateToMixNode(this.mixnetContract, mixIdentity, amount, fee, memo); + return (this.client as ISigningClient).delegateToMixNode(this.mixnetContract, mixId, amount, fee, memo); } public async undelegateFromMixNode( - mixIdentity: string, + mixId: number, fee?: StdFee | 'auto' | number, memo?: string, ): Promise { - return (this.client as ISigningClient).undelegateFromMixNode(this.mixnetContract, mixIdentity, fee, memo); + return (this.client as ISigningClient).undelegateFromMixNode(this.mixnetContract, mixId, fee, memo); } public async updateMixnodeConfig( - mixIdentity: string, + mixId: number, fee: StdFee | 'auto' | number, profitPercentage: number, ): Promise { - return (this.client as ISigningClient).updateMixnodeConfig(this.mixnetContract, mixIdentity, profitPercentage, fee); + return (this.client as ISigningClient).updateMixnodeConfig(this.mixnetContract, mixId, profitPercentage, fee); } public async updateContractStateParams( diff --git a/clients/validator/src/validator-api-querier.ts b/clients/validator/src/nym-api-querier.ts similarity index 52% rename from clients/validator/src/validator-api-querier.ts rename to clients/validator/src/nym-api-querier.ts index 7c61f91bd7..e66c93c41f 100644 --- a/clients/validator/src/validator-api-querier.ts +++ b/clients/validator/src/nym-api-querier.ts @@ -2,36 +2,35 @@ * Copyright 2021 - Nym Technologies SA * SPDX-License-Identifier: Apache-2.0 */ - import axios from 'axios'; -import { GatewayBond, MixNodeBond } from './types'; +import { GatewayBond, MixNodeBond, MixNodeDetails } from '@nymproject/types'; -export const VALIDATOR_API_VERSION = '/v1'; -export const VALIDATOR_API_GATEWAYS_PATH = `${VALIDATOR_API_VERSION}/gateways`; -export const VALIDATOR_API_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes`; -export const VALIDATOR_API_ACTIVE_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/active`; -export const VALIDATOR_API_REWARDED_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/rewarded`; +export const NYM_API_VERSION = '/v1'; +export const NYM_API_GATEWAYS_PATH = `${NYM_API_VERSION}/gateways`; +export const NYM_API_MIXNODES_PATH = `${NYM_API_VERSION}/mixnodes`; +export const NYM_API_ACTIVE_MIXNODES_PATH = `${NYM_API_VERSION}/mixnodes/active`; +export const NYM_API_REWARDED_MIXNODES_PATH = `${NYM_API_VERSION}/mixnodes/rewarded`; -export interface IValidatorApiQuery { +export interface INymApiQuery { getCachedMixnodes(): Promise; getCachedGateways(): Promise; - getActiveMixnodes(): Promise; + getActiveMixnodes(): Promise; getRewardedMixnodes(): Promise; } -export default class ValidatorApiQuerier implements IValidatorApiQuery { - validatorApiUrl: string; +export default class NymApiQuerier implements INymApiQuery { + nymApiUrl: string; - constructor(validatorApiUrl: string) { - this.validatorApiUrl = validatorApiUrl; + constructor(nymApiUrl: string) { + this.nymApiUrl = nymApiUrl; } async getCachedMixnodes(): Promise { - const url = new URL(this.validatorApiUrl); - url.pathname += VALIDATOR_API_MIXNODES_PATH; + const url = new URL(this.nymApiUrl); + url.pathname += NYM_API_MIXNODES_PATH; const response = await axios.get(url.toString()); if (response.status === 200) { @@ -41,8 +40,8 @@ export default class ValidatorApiQuerier implements IValidatorApiQuery { } async getCachedGateways(): Promise { - const url = new URL(this.validatorApiUrl); - url.pathname += VALIDATOR_API_GATEWAYS_PATH; + const url = new URL(this.nymApiUrl); + url.pathname += NYM_API_GATEWAYS_PATH; const response = await axios.get(url.toString()); if (response.status === 200) { @@ -51,9 +50,9 @@ export default class ValidatorApiQuerier implements IValidatorApiQuery { throw new Error('None of the provided validator APIs seem to be alive'); } - async getActiveMixnodes(): Promise { - const url = new URL(this.validatorApiUrl); - url.pathname += VALIDATOR_API_ACTIVE_MIXNODES_PATH; + async getActiveMixnodes(): Promise { + const url = new URL(this.nymApiUrl); + url.pathname += NYM_API_ACTIVE_MIXNODES_PATH; const response = await axios.get(url.toString()); if (response.status === 200) { @@ -63,8 +62,8 @@ export default class ValidatorApiQuerier implements IValidatorApiQuery { } async getRewardedMixnodes(): Promise { - const url = new URL(this.validatorApiUrl); - url.pathname += VALIDATOR_API_REWARDED_MIXNODES_PATH; + const url = new URL(this.nymApiUrl); + url.pathname += NYM_API_REWARDED_MIXNODES_PATH; const response = await axios.get(url.toString()); if (response.status === 200) { diff --git a/clients/validator/src/nymd-querier.ts b/clients/validator/src/nyxd-querier.ts similarity index 58% rename from clients/validator/src/nymd-querier.ts rename to clients/validator/src/nyxd-querier.ts index ee2148d56d..0e34493c2e 100644 --- a/clients/validator/src/nymd-querier.ts +++ b/clients/validator/src/nyxd-querier.ts @@ -2,30 +2,26 @@ * Copyright 2021 - Nym Technologies SA * SPDX-License-Identifier: Apache-2.0 */ - -import { JsonObject } from '@cosmjs/cosmwasm-stargate'; // eslint-disable-next-line import/no-cycle -import { INymdQuery } from './query-client'; +import { INyxdQuery } from './query-client'; +import { Delegation, RewardingParams, StakeSaturationResponse } from '@nymproject/types'; import { - ContractStateParams, - Delegation, + UnbondedMixnodeResponse, GatewayOwnershipResponse, - LayerDistribution, MixnetContractVersion, MixOwnershipResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse, - PagedMixnodeResponse, - RewardingStatus, -} from './types'; + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + PagedUnbondedMixnodesResponse, + LayerDistribution, +} from '@nymproject/types'; +import { ContractState, SmartContractQuery } from './types/shared'; -interface SmartContractQuery { - queryContractSmart(address: string, queryMsg: Record): Promise; -} - -export default class NymdQuerier implements INymdQuery { +export default class NyxdQuerier implements INyxdQuery { client: SmartContractQuery; constructor(client: SmartContractQuery) { @@ -38,15 +34,44 @@ export default class NymdQuerier implements INymdQuery { }); } - getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { + getMixNodeBonds( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - get_mix_nodes: { + get_mix_node_bonds: { limit, start_after: startAfter, }, }); } + getMixNodesDetailed( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.client.queryContractSmart(mixnetContractAddress, { + get_mix_nodes_detailed: { + limit, + start_after: startAfter, + }, + }); + } + + getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise { + return this.client.queryContractSmart(mixnetContractAddress, { + get_stake_saturation: { mix_id: mixId }, + }); + } + + getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise { + return this.client.queryContractSmart(mixnetContractAddress, { + get_mixnode_rewarding_details: { mix_id: mixId }, + }); + } + getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { get_gateways: { @@ -56,35 +81,51 @@ export default class NymdQuerier implements INymdQuery { }); } - ownsMixNode(mixnetContractAddress: string, address: string): Promise { + getOwnedMixnode(mixnetContractAddress: string, address: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - owns_mixnode: { + get_owned_mixnode: { address, }, }); } + getUnbondedMixNodes( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.client.queryContractSmart(mixnetContractAddress, { + get_unbonded_mix_nodes: { limit, start_after: startAfter }, + }); + } + + getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise { + return this.client.queryContractSmart(mixnetContractAddress, { + get_unbonded_mix_node_information: { mix_id: mixId }, + }); + } + ownsGateway(mixnetContractAddress: string, address: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - owns_gateway: { + get_owned_gateway: { address, }, }); } - getStateParams(mixnetContractAddress: string): Promise { + getStateParams(mixnetContractAddress: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - state_params: {}, + get_state: {}, }); } - getAllNetworkDelegationsPaged( + getAllDelegationsPaged( mixnetContractAddress: string, limit?: number, startAfter?: [string, string], ): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - get_all_network_delegations: { + get_all_delegations: { start_after: startAfter, limit, }, @@ -93,13 +134,13 @@ export default class NymdQuerier implements INymdQuery { getMixNodeDelegationsPaged( mixnetContractAddress: string, - mixIdentity: string, + mix_id: number, limit?: number, startAfter?: string, ): Promise { return this.client.queryContractSmart(mixnetContractAddress, { get_mixnode_delegations: { - mix_identity: mixIdentity, + mix_id: mix_id, start_after: startAfter, limit, }, @@ -121,10 +162,10 @@ export default class NymdQuerier implements INymdQuery { }); } - getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise { + getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { get_delegation_details: { - mix_identity: mixIdentity, + mix_id: mix_id, delegator, }, }); @@ -132,44 +173,19 @@ export default class NymdQuerier implements INymdQuery { getLayerDistribution(mixnetContractAddress: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - layer_distribution: {}, + get_layer_distribution: {}, }); } - getRewardPool(mixnetContractAddress: string): Promise { + getRewardParams(mixnetContractAddress: string): Promise { return this.client.queryContractSmart(mixnetContractAddress, { - get_reward_pool: {}, + get_rewarding_params: {}, }); } - getCirculatingSupply(mixnetContractAddress: string): Promise { - return this.client.queryContractSmart(mixnetContractAddress, { - get_circulating_supply: {}, - }); - } - - getIntervalRewardPercent(mixnetContractAddress: string): Promise { - return this.client.queryContractSmart(mixnetContractAddress, { - get_interval_reward_percent: {}, - }); - } - - getSybilResistancePercent(mixnetContractAddress: string): Promise { - return this.client.queryContractSmart(mixnetContractAddress, { - get_sybil_resistance_percent: {}, - }); - } - - getRewardingStatus( - mixnetContractAddress: string, - mixIdentity: string, - rewardingIntervalNonce: number, - ): Promise { - return this.client.queryContractSmart(mixnetContractAddress, { - get_rewarding_status: { - mix_identity: mixIdentity, - rewarding_interval_nonce: rewardingIntervalNonce, - }, + getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise { + return this.client.queryContractSmart(vestingContractAddress, { + vesting_account_address: vestingAccountAddress, }); } } diff --git a/clients/validator/src/query-client.ts b/clients/validator/src/query-client.ts index 9e3937a89e..319c46f80e 100644 --- a/clients/validator/src/query-client.ts +++ b/clients/validator/src/query-client.ts @@ -1,75 +1,56 @@ -import { CosmWasmClient, JsonObject } from '@cosmjs/cosmwasm-stargate'; -import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; -import { - Account, - Block, - Coin, - DeliverTxResponse, - IndexedTx, - SearchTxFilter, - SearchTxQuery, - SequenceResponse, -} from '@cosmjs/stargate'; -import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient'; +import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'; // eslint-disable-next-line import/no-cycle -import NymdQuerier from './nymd-querier'; +import NyxdQuerier from './nyxd-querier'; import { - ContractStateParams, Delegation, GatewayBond, GatewayOwnershipResponse, LayerDistribution, MixnetContractVersion, - MixNodeBond, + MixNodeDetails, MixOwnershipResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse, - PagedMixnodeResponse, - RewardingStatus, -} from './types'; -import ValidatorApiQuerier, { IValidatorApiQuery } from './validator-api-querier'; + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + PagedUnbondedMixnodesResponse, + StakeSaturationResponse, + UnbondedMixnodeResponse, + MixNodeBond, + MixNodeRewarding, +} from '@nymproject/types'; +import NymApiQuerier, { INymApiQuery } from './nym-api-querier'; +import { ContractState, ICosmWasmQuery } from './types/shared'; +import { RewardingParams } from '@nymproject/types'; +import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; -export interface ICosmWasmQuery { - // methods exposed by `CosmWasmClient` - getChainId(): Promise; - getHeight(): Promise; - getAccount(searchAddress: string): Promise; - getSequence(address: string): Promise; - getBlock(height?: number): Promise; - getBalance(address: string, searchDenom: string): Promise; - getTx(id: string): Promise; - searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise; - disconnect(): void; - broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise; - getCodes(): Promise; - getCodeDetails(codeId: number): Promise; - getContracts(codeId: number): Promise; - getContract(address: string): Promise; - getContractCodeHistory(address: string): Promise; - queryContractRaw(address: string, key: Uint8Array): Promise; - queryContractSmart(address: string, queryMsg: Record): Promise; -} - -export interface INymdQuery { +export interface INyxdQuery { // nym-specific implemented inside NymQuerier getContractVersion(mixnetContractAddress: string): Promise; - - getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise; + getMixNodeBonds( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise; + getMixNodesDetailed( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise; getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise; - ownsMixNode(mixnetContractAddress: string, address: string): Promise; + getOwnedMixnode(mixnetContractAddress: string, address: string): Promise; ownsGateway(mixnetContractAddress: string, address: string): Promise; - getStateParams(mixnetContractAddress: string): Promise; - - getAllNetworkDelegationsPaged( + getStateParams(mixnetContractAddress: string): Promise; + getAllDelegationsPaged( mixnetContractAddress: string, limit?: number, startAfter?: [string, string], ): Promise; getMixNodeDelegationsPaged( mixnetContractAddress: string, - mixIdentity: string, + mix_id: number, limit?: number, startAfter?: string, ): Promise; @@ -79,77 +60,103 @@ export interface INymdQuery { limit?: number, startAfter?: string, ): Promise; - getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise; - + getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise; getLayerDistribution(mixnetContractAddress: string): Promise; - getRewardPool(mixnetContractAddress: string): Promise; - getCirculatingSupply(mixnetContractAddress: string): Promise; - getIntervalRewardPercent(mixnetContractAddress: string): Promise; - getSybilResistancePercent(mixnetContractAddress: string): Promise; - getRewardingStatus( - mixnetContractAddress: string, - mixIdentity: string, - rewardingIntervalNonce: number, - ): Promise; + getRewardParams(mixnetContractAddress: string): Promise; + getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise; + getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise; + getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise; } -export interface IQueryClient extends ICosmWasmQuery, INymdQuery, IValidatorApiQuery {} +export interface IQueryClient extends ICosmWasmQuery, INyxdQuery, INymApiQuery {} export default class QueryClient extends CosmWasmClient implements IQueryClient { - private nymdQuerier: NymdQuerier; + private nyxdQuerier: NyxdQuerier; - private validatorApiQuerier: ValidatorApiQuerier; + private nymApiQuerier: NymApiQuerier; - private constructor(tmClient: Tendermint34Client, validatorApiUrl: string) { + private constructor(tmClient: Tendermint34Client, nymApiUrl: string) { super(tmClient); - this.nymdQuerier = new NymdQuerier(this); - this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl); + this.nyxdQuerier = new NyxdQuerier(this); + this.nymApiQuerier = new NymApiQuerier(nymApiUrl); } - public static async connectWithNym(nymdUrl: string, validatorApiUrl: string): Promise { - const tmClient = await Tendermint34Client.connect(nymdUrl); - return new QueryClient(tmClient, validatorApiUrl); + public static async connectWithNym(nyxdUrl: string, nymApiUrl: string): Promise { + const tmClient = await Tendermint34Client.connect(nyxdUrl); + return new QueryClient(tmClient, nymApiUrl); } getContractVersion(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getContractVersion(mixnetContractAddress); + return this.nyxdQuerier.getContractVersion(mixnetContractAddress); } - getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { - return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter); + getMixNodeBonds( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getMixNodeBonds(mixnetContractAddress, limit, startAfter); + } + + getMixNodesDetailed( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getMixNodesDetailed(mixnetContractAddress, limit, startAfter); + } + + getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise { + return this.nyxdQuerier.getStakeSaturation(mixnetContractAddress, mixId); + } + + getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise { + return this.nyxdQuerier.getMixnodeRewardingDetails(mixnetContractAddress, mixId); } getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { - return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter); + return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter); } - ownsMixNode(mixnetContractAddress: string, address: string): Promise { - return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address); + getOwnedMixnode(mixnetContractAddress: string, address: string): Promise { + return this.nyxdQuerier.getOwnedMixnode(mixnetContractAddress, address); } ownsGateway(mixnetContractAddress: string, address: string): Promise { - return this.nymdQuerier.ownsGateway(mixnetContractAddress, address); + return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address); } - getStateParams(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getStateParams(mixnetContractAddress); + getUnbondedMixNodes( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getUnbondedMixNodes(mixnetContractAddress, limit, startAfter); } - getAllNetworkDelegationsPaged( + getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise { + return this.nyxdQuerier.getUnbondedMixNodeInformation(mixnetContractAddress, mixId); + } + + getStateParams(mixnetContractAddress: string): Promise { + return this.nyxdQuerier.getStateParams(mixnetContractAddress); + } + + getAllDelegationsPaged( mixnetContractAddress: string, limit?: number, startAfter?: [string, string], ): Promise { - return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter); + return this.nyxdQuerier.getAllDelegationsPaged(mixnetContractAddress, limit, startAfter); } getMixNodeDelegationsPaged( mixnetContractAddress: string, - mixIdentity: string, + mix_id: number, limit?: number, startAfter?: string, ): Promise { - return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter); + return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mix_id, limit, startAfter); } getDelegatorDelegationsPaged( @@ -158,54 +165,38 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient limit?: number, startAfter?: string, ): Promise { - return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter); + return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter); } - getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise { - return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator); + getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise { + return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mix_id, delegator); } getLayerDistribution(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getLayerDistribution(mixnetContractAddress); + return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress); } - getRewardPool(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getRewardPool(mixnetContractAddress); - } - - getCirculatingSupply(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress); - } - - getIntervalRewardPercent(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getIntervalRewardPercent(mixnetContractAddress); - } - - getSybilResistancePercent(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress); - } - - getRewardingStatus( - mixnetContractAddress: string, - mixIdentity: string, - rewardingIntervalNonce: number, - ): Promise { - return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce); + getRewardParams(mixnetContractAddress: string): Promise { + return this.nyxdQuerier.getRewardParams(mixnetContractAddress); } getCachedGateways(): Promise { - return this.validatorApiQuerier.getCachedGateways(); + return this.nymApiQuerier.getCachedGateways(); } getCachedMixnodes(): Promise { - return this.validatorApiQuerier.getCachedMixnodes(); + return this.nymApiQuerier.getCachedMixnodes(); } - getActiveMixnodes(): Promise { - return this.validatorApiQuerier.getActiveMixnodes(); + getActiveMixnodes(): Promise { + return this.nymApiQuerier.getActiveMixnodes(); } getRewardedMixnodes(): Promise { - return this.validatorApiQuerier.getRewardedMixnodes(); + return this.nymApiQuerier.getRewardedMixnodes(); + } + + getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise { + return this.nyxdQuerier.getSpendableCoins(vestingContractAddress, vestingAccountAddress); } } diff --git a/clients/validator/src/signing-client.ts b/clients/validator/src/signing-client.ts index ef8913243d..d750eefcd2 100644 --- a/clients/validator/src/signing-client.ts +++ b/clients/validator/src/signing-client.ts @@ -14,7 +14,7 @@ import { ChangeAdminResult } from '@cosmjs/cosmwasm-stargate/build/signingcosmwa import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx'; import { nymGasPrice } from './stargate-helper'; import { IQueryClient } from './query-client'; -import NymdQuerier from './nymd-querier'; +import NyxdQuerier from './nyxd-querier'; import { ContractStateParams, Delegation, @@ -25,15 +25,22 @@ import { MixnetContractVersion, MixNode, MixNodeBond, + MixNodeCostParams, + MixNodeDetails, + MixNodeRewarding, MixOwnershipResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse, - PagedMixnodeResponse, - RewardingStatus, -} from './types'; -import ValidatorApiQuerier from './validator-api-querier'; + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + PagedUnbondedMixnodesResponse, + RewardingParams, + UnbondedMixnodeResponse, +} from '@nymproject/types'; +import NymApiQuerier from './nym-api-querier'; +import { ContractState } from './types/shared'; // methods exposed by `SigningCosmWasmClient` export interface ICosmWasmSigning { @@ -143,6 +150,7 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign bondMixNode( mixnetContractAddress: string, mixNode: MixNode, + costParams: MixNodeCostParams, ownerSignature: string, pledge: Coin, fee?: StdFee | 'auto' | number, @@ -164,7 +172,7 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign delegateToMixNode( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, amount: Coin, fee?: StdFee | 'auto' | number, memo?: string, @@ -172,14 +180,14 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign undelegateFromMixNode( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, fee?: StdFee | 'auto' | number, memo?: string, ): Promise; updateMixnodeConfig( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, profitMarginPercent: number, fee: StdFee | 'auto' | number, ): Promise; @@ -197,29 +205,29 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign } export default class SigningClient extends SigningCosmWasmClient implements ISigningClient { - private nymdQuerier: NymdQuerier; + private nyxdQuerier: NyxdQuerier; - private validatorApiQuerier: ValidatorApiQuerier; + private nymApiQuerier: NymApiQuerier; clientAddress: string; private constructor( clientAddress: string, - validatorApiUrl: string, + nymApiUrl: string, tmClient: Tendermint34Client, wallet: DirectSecp256k1HdWallet, signerOptions: SigningCosmWasmClientOptions, ) { super(tmClient, wallet, signerOptions); this.clientAddress = clientAddress; - this.nymdQuerier = new NymdQuerier(this); - this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl); + this.nyxdQuerier = new NyxdQuerier(this); + this.nymApiQuerier = new NymApiQuerier(nymApiUrl); } public static async connectWithNymSigner( wallet: DirectSecp256k1HdWallet, - nymdUrl: string, - validatorApiUrl: string, + nyxdUrl: string, + nymApiUrl: string, prefix: string, denom: string, ): Promise { @@ -228,51 +236,83 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig prefix, gasPrice: nymGasPrice(denom), }; - const tmClient = await Tendermint34Client.connect(nymdUrl); - return new SigningClient(address, validatorApiUrl, tmClient, wallet, signerOptions); + const tmClient = await Tendermint34Client.connect(nyxdUrl); + return new SigningClient(address, nymApiUrl, tmClient, wallet, signerOptions); } // query related: getContractVersion(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getContractVersion(mixnetContractAddress); + return this.getContractVersion(mixnetContractAddress); } - getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { - return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter); + getMixNodeBonds( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getMixNodeBonds(mixnetContractAddress, limit, startAfter); + } + + getMixNodesDetailed( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getMixNodesDetailed(mixnetContractAddress, limit, startAfter); + } + + getStakeSaturation(mixnetContractAddress: string, mixId: number) { + return this.nyxdQuerier.getStakeSaturation(mixnetContractAddress, mixId); + } + + getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise { + return this.nyxdQuerier.getUnbondedMixNodeInformation(mixnetContractAddress, mixId); + } + + getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise { + return this.nyxdQuerier.getMixnodeRewardingDetails(mixnetContractAddress, mixId); } getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise { - return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter); + return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter); } - ownsMixNode(mixnetContractAddress: string, address: string): Promise { - return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address); + getOwnedMixnode(mixnetContractAddress: string, address: string): Promise { + return this.nyxdQuerier.getOwnedMixnode(mixnetContractAddress, address); } ownsGateway(mixnetContractAddress: string, address: string): Promise { - return this.nymdQuerier.ownsGateway(mixnetContractAddress, address); + return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address); } - getStateParams(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getStateParams(mixnetContractAddress); + getStateParams(mixnetContractAddress: string): Promise { + return this.nyxdQuerier.getStateParams(mixnetContractAddress); } - getAllNetworkDelegationsPaged( + getAllDelegationsPaged( mixnetContractAddress: string, limit?: number, startAfter?: [string, string], ): Promise { - return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter); + return this.getAllDelegationsPaged(mixnetContractAddress, limit, startAfter); + } + + getUnbondedMixNodes( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise { + return this.nyxdQuerier.getUnbondedMixNodes(mixnetContractAddress, limit, startAfter); } getMixNodeDelegationsPaged( mixnetContractAddress: string, - mixIdentity: string, + mix_id: number, limit?: number, startAfter?: string, ): Promise { - return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter); + return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mix_id, limit, startAfter); } getDelegatorDelegationsPaged( @@ -281,55 +321,39 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig limit?: number, startAfter?: string, ): Promise { - return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter); + return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter); } - getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise { - return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator); + getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise { + return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mix_id, delegator); } getLayerDistribution(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getLayerDistribution(mixnetContractAddress); + return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress); } - getRewardPool(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getRewardPool(mixnetContractAddress); - } - - getCirculatingSupply(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress); - } - - getIntervalRewardPercent(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getIntervalRewardPercent(mixnetContractAddress); - } - - getSybilResistancePercent(mixnetContractAddress: string): Promise { - return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress); - } - - getRewardingStatus( - mixnetContractAddress: string, - mixIdentity: string, - rewardingIntervalNonce: number, - ): Promise { - return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce); + getRewardParams(mixnetContractAddress: string): Promise { + return this.nyxdQuerier.getRewardParams(mixnetContractAddress); } getCachedGateways(): Promise { - return this.validatorApiQuerier.getCachedGateways(); + return this.nymApiQuerier.getCachedGateways(); } getCachedMixnodes(): Promise { - return this.validatorApiQuerier.getCachedMixnodes(); + return this.nymApiQuerier.getCachedMixnodes(); } - getActiveMixnodes(): Promise { - return this.validatorApiQuerier.getActiveMixnodes(); + getActiveMixnodes(): Promise { + return this.nymApiQuerier.getActiveMixnodes(); } getRewardedMixnodes(): Promise { - return this.validatorApiQuerier.getRewardedMixnodes(); + return this.nymApiQuerier.getRewardedMixnodes(); + } + + getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise { + return this.nyxdQuerier.getSpendableCoins(vestingContractAddress, vestingAccountAddress); } // signing related: @@ -337,6 +361,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig bondMixNode( mixnetContractAddress: string, mixNode: MixNode, + costParams: MixNodeCostParams, ownerSignature: string, pledge: Coin, fee: StdFee | 'auto' | number = 'auto', @@ -348,6 +373,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig { bond_mixnode: { mix_node: mixNode, + cost_params: costParams, owner_signature: ownerSignature, }, }, @@ -414,7 +440,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig delegateToMixNode( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, amount: Coin, fee: StdFee | 'auto' | number = 'auto', memo = 'Default MixNode Delegation from Typescript', @@ -424,7 +450,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig mixnetContractAddress, { delegate_to_mixnode: { - mix_identity: mixIdentity, + mix_id: mixId, }, }, fee, @@ -435,7 +461,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig undelegateFromMixNode( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, fee: StdFee | 'auto' | number = 'auto', memo = 'Default MixNode Undelegation from Typescript', ): Promise { @@ -444,7 +470,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig mixnetContractAddress, { undelegate_from_mixnode: { - mix_identity: mixIdentity, + mix_id: mixId, }, }, fee, @@ -454,14 +480,14 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig updateMixnodeConfig( mixnetContractAddress: string, - mixIdentity: string, + mixId: number, profitMarginPercent: number, fee: StdFee | 'auto' | number, ): Promise { return this.execute( this.clientAddress, mixnetContractAddress, - { update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_identity: mixIdentity } }, + { update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_id: mixId } }, fee, ); } diff --git a/clients/validator/src/tests/expectedResponses.ts b/clients/validator/src/tests/expectedResponses.ts new file mode 100644 index 0000000000..876f61e866 --- /dev/null +++ b/clients/validator/src/tests/expectedResponses.ts @@ -0,0 +1,178 @@ +import expect from 'expect'; + +export const amountDemon = { + amount: expect.any(String), + denom: expect.any(String) +} + +export const delegation = { + owner: expect.any(String), + mix_id: expect.any(Number), + cumulative_reward_ratio: expect.any(String), + amount: amountDemon, + height: expect.any(Number || BigInt), + proxy: expect.any(String || null) +} + +export const detailedDelegation = { + delegation: delegation, + mixnode_still_bonded: expect.any(Boolean) +} + +export const gateway = { + pledge_amount: amountDemon, + owner: expect.any(String), + block_height: expect.any(Number || BigInt), + gateway: { + host: expect.any(String), + mix_port: expect.any(Number), + clients_port: expect.any(Number), + location: expect.any(String), + sphinx_key: expect.any(String), + identity_key: expect.any(String), + version: expect.any(String), + }, + proxy: expect.any(String || null) +} + +export const pagedGateway = { + nodes: gateway, + per_page: expect.any(Number), + start_next_after: expect.any(Number) +} + +export const ownGateway = { + address: expect.any(String), + gateway: gateway +} + +export const rewardingdetails = { + cost_params: { + profit_margin_percent: expect.any(String), + interval_operating_cost: { + denom: expect.any(String), + amount: expect.any(String) + } + }, + operator: expect.any(String), + delegates: expect.any(String), + total_unit_reward: expect.any(String), + unit_delegation: expect.any(String), + last_rewarded_epoch: expect.any(Number), + unique_delegations: expect.any(Number) +} + +export const mix_node = { + host: expect.any(String), + mix_port: expect.any(Number), + verloc_port: expect.any(Number), + http_api_port: expect.any(Number), + sphinx_key: expect.any(String), + identity_key: expect.any(String), + version: expect.any(String) +} + +export const mixnodebond = { + mix_id: expect.any(Number), + owner: expect.any(String), + original_pledge: { + denom: expect.any(String), + amount: expect.any(String) + }, + layer: expect.any(String), + mix_node: mix_node, + proxy: expect.any(String) || null, + bonding_height: expect.any(Number || BigInt), + is_unbonding: expect.any(Boolean) +} + +export const mixnode = { + bond_information: mixnodebond, + rewarding_details: rewardingdetails +} + +export const ownedNode = { + address: expect.any(String), + mixnode_details: { + bond_information: mixnodebond, + rewarding_details: rewardingdetails + } +} + +export const saturation = { + mix_id: expect.any(Number), + current_saturation: expect.any(String), + uncapped_saturation: expect.any(String) +} + +export const contractVersion = { + build_timestamp: expect.any(String), + build_version: expect.any(String), + commit_sha: expect.any(String), + commit_timestamp: expect.any(String), + commit_branch: expect.any(String), + rustc_version: expect.any(String) +}; + +export const stateParams = { + minimum_gateway_pledge: amountDemon, + minimum_mixnode_pledge: expect.any(String), + mixnode_rewarded_set_size: expect.any(Number), + mixnode_active_set_size: expect.any(Number) +} + +export const contract = { + owner: expect.any(Number), + rewarding_validator_address: expect.any(Number), + vesting_contract_address: expect.any(Number), + rewarding_denom: expect.any(String), + params: stateParams +} + +export const rewardingnode = { + mix_id: expect.any(Number), + rewarding_details: rewardingdetails +} + +export const unbondednode = { + mix_id: expect.any(Number), + unbonded_info: { + identity_key: expect.any(String), + owner: expect.any(String), + proxy: expect.any(String) || null, + unbonding_height: expect.any(Number) + } +} + +export const allunbondednodes = [ + expect.any(Number), { + identity_key: expect.any(String), + owner: expect.any(String), + proxy: expect.any(String) || null, + unbonding_height: expect.any(Number) + } +] + +export const layerDistribution = { + layer1: expect.any(Number), + layer2: expect.any(Number), + layer3: expect.any(Number) +} + + +export const intervalRewardParams = { + reward_pool: expect.any(Number), + staking_supply: expect.any(Number), + staking_supply_scale_factor: expect.any(Number), + epoch_reward_budget: expect.any(Number), + stake_saturation_point: expect.any(Number), + sybil_resistance: expect.any(Number), + active_set_work_factor: expect.any(Number), + interval_pool_emission: expect.any(Number) +} + +export const rewardingParams = { + interval: intervalRewardParams, + rewarded_set_size: expect.any(Number), + active_set_size: expect.any(Number) +} diff --git a/clients/validator/src/tests/mock/client.ts b/clients/validator/src/tests/mock/client.ts new file mode 100644 index 0000000000..c5633d2ed0 --- /dev/null +++ b/clients/validator/src/tests/mock/client.ts @@ -0,0 +1,19 @@ +import { Mock, Times } from 'moq.ts'; +import expect from 'expect'; +import { INyxdQuery } from '../../query-client'; + +export class TestHelper { + buildMethod = async (methodName: string, args: any[], expectedResult: any): Promise => { + const client = new Mock() + .setup((nym) => nym[methodName](...args)) + .returns(Promise.resolve(expectedResult)); + const obj = client.object(); + const actualDetails = await obj[methodName](...args); + + client.verify((nym) => nym[methodName](...args), Times.Exactly(1)); + expect(Object.keys([actualDetails])).toEqual(Object.keys(expectedResult)); + expect(actualDetails).toBeDefined(); + + return actualDetails; + }; +} diff --git a/clients/validator/src/tests/mock/delegation.mock.test.ts b/clients/validator/src/tests/mock/delegation.mock.test.ts new file mode 100644 index 0000000000..69fffcc53b --- /dev/null +++ b/clients/validator/src/tests/mock/delegation.mock.test.ts @@ -0,0 +1,40 @@ +import expect from 'expect'; +import { Delegation } from '@nymproject/types'; +import { PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse } from '../../types/shared-types'; +import { TestHelper } from './client'; +import { mixnet, mixnodeowneraddress, mix_id } from './testData'; + +describe('Delegation mock tests', () => { + const testHelper = new TestHelper(); + + it('get Delegation Details', () => { + const execute = testHelper.buildMethod('getDelegationDetails', [mixnet, mix_id, mixnodeowneraddress], { + owner: mixnodeowneraddress, + mix_id: 0, + cumulative_reward_ratio: '', + amount: { + denom: 'nym', + amount: '10', + }, + height: 1314134144132n, + proxy: 'null', + }); + expect(execute).toBeTruthy(); + }); + + it('get All Delegations Paged', () => { + const execute = testHelper.buildMethod('getAllDelegationsPaged', [mixnet], { + delegations: [], + }); + expect(execute).toBeTruthy(); + }); + + it('get Delegator Delegations Paged', () => { + const execute = testHelper.buildMethod('getDelegatorDelegationsPaged', [mixnet, mixnodeowneraddress], < + PagedDelegatorDelegationsResponse + >{ + delegations: [], + }); + expect(execute).toBeTruthy(); + }); +}); diff --git a/clients/validator/src/tests/mock/gateway.mock.test.ts b/clients/validator/src/tests/mock/gateway.mock.test.ts new file mode 100644 index 0000000000..5c7f277a41 --- /dev/null +++ b/clients/validator/src/tests/mock/gateway.mock.test.ts @@ -0,0 +1,24 @@ +import expect from 'expect'; +import { GatewayOwnershipResponse, PagedGatewayResponse } from '../../types/shared-types'; +import { TestHelper } from './client'; +import { gatewayowneraddress, mixnet } from './testData'; + +describe('Gateway mock tests', () => { + const testHelper = new TestHelper(); + + it('get Gateways Paged', () => { + const execute = testHelper.buildMethod('getGatewaysPaged', [mixnet], { + nodes: [], + per_page: 25, + }); + expect(execute).toBeTruthy(); + }); + + it('owns Gateway', () => { + const execute = testHelper.buildMethod('ownsGateway', [mixnet, gatewayowneraddress], { + address: gatewayowneraddress, + gateway: {}, + }); + expect(execute).toBeTruthy(); + }); +}); diff --git a/clients/validator/src/tests/mock/mixnet.mock.test.ts b/clients/validator/src/tests/mock/mixnet.mock.test.ts new file mode 100644 index 0000000000..8d84d4eecc --- /dev/null +++ b/clients/validator/src/tests/mock/mixnet.mock.test.ts @@ -0,0 +1,65 @@ +import expect from 'expect'; +import { LayerDistribution, MixnetContractVersion, StakeSaturationResponse } from '@nymproject/types'; +import { TestHelper } from './client'; +import { mixnet, mix_id } from './testData'; +import { RewardingParams } from '@nymproject/types'; +import { ContractState } from '../../types/shared'; + +describe('Mixnet mock tests', () => { + const testHelper = new TestHelper(); + + it('get Layer Distribution', () => { + const execute = testHelper.buildMethod('getLayerDistribution', [mixnet], { + layer1: 2, + layer2: 2, + layer3: 5, + }); + expect(execute).toBeTruthy(); + }); + + it('get Reward Params', () => { + const execute = testHelper.buildMethod('getRewardParams', [mixnet], { + interval: {}, + rewarded_set_size: 0, + active_set_size: 0, + }); + expect(execute).toBeTruthy(); + }); + + it('get Stake Saturation', () => { + const execute = testHelper.buildMethod('getStakeSaturation', [mixnet, mix_id], { + mix_id: 0, + current_saturation: '', + uncapped_saturation: '', + }); + expect(execute).toBeTruthy(); + }); + + it('get State Params', () => { + const execute = testHelper.buildMethod('getStateParams', [mixnet], { + owner: '', + rewarding_validator_address: '', + vesting_contract_address: '', + rewarding_denom: 'unym', + params: { + minimum_mixnode_pledge: '', + minimum_gateway_pledge: '', + mixnode_rewarded_set_size: 240, + mixnode_active_set_size: 240, + }, + }); + expect(execute).toBeTruthy(); + }); + + it('get Contract Version', () => { + const execute = testHelper.buildMethod('getContractVersion', [mixnet], { + build_timestamp: 'test', + commit_branch: 'test', + build_version: 'test', + rustc_version: 'test', + commit_sha: 'test', + commit_timestamp: 'test', + }); + expect(execute).toBeTruthy(); + }); +}); diff --git a/clients/validator/src/tests/mock/mixnode.mock.test.ts b/clients/validator/src/tests/mock/mixnode.mock.test.ts new file mode 100644 index 0000000000..c4d9e4519b --- /dev/null +++ b/clients/validator/src/tests/mock/mixnode.mock.test.ts @@ -0,0 +1,71 @@ +import expect from 'expect'; +import { + MixNodeRewarding, + MixOwnershipResponse, + PagedMixDelegationsResponse, + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + UnbondedMixnodeResponse, +} from '@nymproject/types'; +import { TestHelper } from './client'; +import { mixnet, mixnodeowneraddress, mix_id } from './testData'; + +describe('Mixnode mock tests', () => { + const testHelper = new TestHelper(); + + it('get Mixnode Bonds', () => { + const execute = testHelper.buildMethod('getMixNodeBonds', [mixnet], { + nodes: [], + per_page: 25, + }); + expect(execute).toBeTruthy(); + }); + + it('get Mixnode Delegations Paged', () => { + const execute = testHelper.buildMethod('getMixNodeDelegationsPaged', [mixnet, mix_id], < + PagedMixDelegationsResponse + >{ + delegations: [], + per_page: 25, + }); + expect(execute).toBeTruthy(); + }); + + it('get Mixnodes Detailed', () => { + const execute = testHelper.buildMethod('getMixNodesDetailed', [mixnet], { + nodes: [], + per_page: 25, + }); + expect(execute).toBeTruthy(); + }); + + it('get Mixnode Rewarding Details', () => { + const execute = testHelper.buildMethod('getMixnodeRewardingDetails', [mixnet, mix_id], { + cost_params: {}, + operator: '', + delegates: '', + total_unit_reward: '', + unit_delegation: '', + last_rewarded_epoch: 1, + unique_delegations: 1, + }); + expect(execute).toBeTruthy(); + }); + + it('get Owned Mixnode', () => { + const execute = testHelper.buildMethod('getOwnedMixnode', [mixnet, mixnodeowneraddress], { + address: '', + mixnode: {}, + }); + expect(execute).toBeTruthy(); + }); + + it('get Unbonded Mixnode Information', () => { + const execute = testHelper.buildMethod( + 'getUnbondedMixNodeInformation', + [mixnet, mix_id], + {}, + ); + expect(execute).toBeTruthy(); + }); +}); diff --git a/clients/validator/src/tests/mock/testData.ts b/clients/validator/src/tests/mock/testData.ts new file mode 100644 index 0000000000..28df1557f7 --- /dev/null +++ b/clients/validator/src/tests/mock/testData.ts @@ -0,0 +1,8 @@ +export const mixnet = 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g'; +export const gatewayowneraddress = 'n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts'; +// export const mixId = 436207616; +export const mix_id = 26; +export const nodeIdentityKey = 'ATmVJknZarDF6Yj53M7h8NS9LLCUvWuToXpk3pDvYUH1'; +export const mixnodeowneraddress = 'n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47'; +export const delegatorAddress = 'n1lemst75va9700tsrxq58adzujrh6h9s5x60h9h'; +export const rewardingIntervalNonce = 1; diff --git a/clients/validator/src/tests/query/mixnetQueries.test.ts b/clients/validator/src/tests/query/mixnetQueries.test.ts new file mode 100644 index 0000000000..391f8aaf0d --- /dev/null +++ b/clients/validator/src/tests/query/mixnetQueries.test.ts @@ -0,0 +1,180 @@ +import expect from 'expect'; +import ValidatorClient from '../../index'; + +import { + allunbondednodes, + contract, + delegation, + gateway, + layerDistribution, + mixnode, + mixnodebond, + ownedNode, + ownGateway, + rewardingnode, + saturation, + unbondednode, +} from '../expectedResponses'; +import { delegatorAddress, gatewayowneraddress } from '../mock/testData'; + +const dotenv = require('dotenv'); + +dotenv.config(); + +describe('Mixnet queries', () => { + let client: ValidatorClient; + + beforeEach(async () => { + client = await ValidatorClient.connectForQuery( + process.env.rpcAddress || '', + process.env.validatorAddress || '', + process.env.prefix || '', + process.env.mixnetContractAddress || '', + process.env.vestingContractAddress || '', + process.env.denom || '', + ); + }); + + // + // CONTRACT + // + + it('can query for an account balance', async () => { + const balance = await client.getBalance('n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77'); + expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0); + }); + + it('can query for stake saturation', async () => { + const stakeSaturation = await client.getStakeSaturation(7); + expect(Object.keys(stakeSaturation)).toEqual(Object.keys(saturation)); + expect(stakeSaturation).toBeTruthy(); + expect(stakeSaturation?.current_saturation).toBeTruthy(); + }); + + it('can query for contract version', async () => { + const contractV = await client.getMixnetContractVersion(); + expect(contractV).toBeTruthy(); + }); + + it('can query for mixnet contract settings', async () => { + const settings = await client.getMixnetContractSettings(); + expect(Object.keys(settings)).toEqual(Object.keys(contract)); + expect(settings).toBeTruthy(); + }); + + it('can query for reward pool', async () => { + const rewardPool = await client.getRewardParams(); + // TODO add velidation here + expect(rewardPool).toBeTruthy(); + }); + + it('can query for layer distribution', async () => { + const layer = await client.getLayerDistribution(); + expect(Object.keys(layer)).toEqual(Object.keys(layerDistribution)); + expect(layer).toBeTruthy(); + }); + + // + // MIXNODES + // + + it('can query for unbonded mixnodes', async () => { + const unbondedNodes = await client.getUnbondedMixNodes(); + for (let i = 0; i < unbondedNodes.length; i++) { + expect(Object.keys(unbondedNodes[0])).toEqual(Object.keys(allunbondednodes)); + expect(unbondedNodes).toBeTruthy(); + } + }); + + it('can query for unbonded mixnode information', async () => { + const unbondedMixnodeInfo = await client.getUnbondedMixNodeInformation(1); + expect(Object.keys(unbondedMixnodeInfo)).toEqual(Object.keys(unbondednode)); + expect(unbondedMixnodeInfo).toBeTruthy(); + }); + + it('can query for mixnode rewarding details', async () => { + const rewardingDetails = await client.getMixnodeRewardingDetails(1); + expect(Object.keys(rewardingDetails)).toEqual(Object.keys(rewardingnode)); + expect(rewardingDetails).toBeTruthy(); + }); + + it('can query for owned mixnode', async () => { + const ownedMixnode = await client.getOwnedMixnode('n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77'); + expect(Object.keys(ownedMixnode)).toEqual(Object.keys(ownedNode)); + expect(ownedMixnode).toBeTruthy(); + }); + + it('can query for all mixnode bonds', async () => { + const mixnodeBonds = await client.getMixNodeBonds(); + expect(Object.keys(mixnodeBonds[0])).toEqual(Object.keys(mixnodebond)); + expect(mixnodeBonds).toBeTruthy(); + expect(Array.isArray(mixnodeBonds)).toBeTruthy(); + }); + + it('can query for all mixnode details', async () => { + const mixnodeDetails = await client.getMixNodesDetailed(); + expect(Object.keys(mixnodeDetails[0])).toEqual(Object.keys(mixnode)); + expect(mixnodeDetails).toBeTruthy(); + expect(Array.isArray(mixnodeDetails)).toBeTruthy(); + }); + + it('can query for all active mixnodes', async () => { + const activeNodes = await client.getActiveMixnodes(); + expect(Object.keys(activeNodes[0])).toEqual(Object.keys(mixnode)); + expect(activeNodes).toBeTruthy(); + expect(Array.isArray(activeNodes)).toBeTruthy(); + }); + + it('can query for rewarded mixnodes', async () => { + const rewardNodes = await client.getRewardedMixnodes(); + expect(Object.keys(rewardNodes[0])).toEqual(Object.keys(mixnode)); + expect(rewardNodes).toBeTruthy(); + }); + + // + // DELEGATIONS + // + + it('can query for account delegations', async () => { + const delegations = await client.getAllNyxdDelegatorDelegations('n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47'); + expect(Object.keys(delegations[0])).toEqual(Object.keys(delegation)); + expect(delegations).toBeTruthy(); + expect(Array.isArray(delegations)).toBeTruthy(); + }); + + it('can query for all delegations', async () => { + const allDelegations = await client.getAllNyxdDelegations(); + expect(Object.keys(allDelegations[0])).toEqual(Object.keys(delegation)); + expect(allDelegations).toBeTruthy(); + expect(Array.isArray(allDelegations)).toBeTruthy(); + }); + + it('can query for all delegations on a node', async () => { + const mixnodeDelegations = await client.getAllNyxdSingleMixnodeDelegations(1); + expect(Object.keys(mixnodeDelegations[0])).toEqual(Object.keys(delegation)); + expect(mixnodeDelegations).toBeTruthy(); + }); + + it('can query for detailed delegations', async () => { + const detailedDelegation = await client.getDelegationDetails(7, delegatorAddress); + expect(Object.keys(detailedDelegation)).toEqual(Object.keys(detailedDelegation)); + expect(detailedDelegation).toBeTruthy(); + }); + + // + // GATEWAYS + // + + it('can query for all gateways', async () => { + const gateways = await client.getAllNyxdGateways(); + expect(Object.keys(gateways[0])).toEqual(Object.keys(gateway)); + expect(gateways).toBeTruthy(); + expect(Array.isArray(gateways)).toBeTruthy(); + }).timeout(10000); + + it('can query for owned gateway', async () => { + const gateway = await client.ownsGateway(gatewayowneraddress); + expect(Object.keys(gateway)).toEqual(Object.keys(ownGateway)); + expect(gateway).toBeTruthy(); + }).timeout(10000); +}); diff --git a/clients/validator/src/tests/query/vestingQueries.test.ts b/clients/validator/src/tests/query/vestingQueries.test.ts new file mode 100644 index 0000000000..2cbe4f5088 --- /dev/null +++ b/clients/validator/src/tests/query/vestingQueries.test.ts @@ -0,0 +1,28 @@ +import expect from 'expect'; +import ValidatorClient from '../../index'; + +const dotenv = require('dotenv'); + +dotenv.config(); + +describe('Vesting queries', () => { + let client: ValidatorClient; + + beforeEach(async () => { + client = await ValidatorClient.connectForQuery( + process.env.rpcAddress || '', + process.env.validatorAddress || '', + process.env.prefix || '', + process.env.mixnetContractAddress || '', + process.env.vestingContractAddress || '', + process.env.denom || '', + ); + }); + + it('can query for contract version', async () => { + const contract = await client.getVestingContractVersion(); + expect(contract).toBeTruthy(); + }); + + it('can get the balance on the account', () => {}); +}); diff --git a/clients/validator/src/tests/sign/mixnetActions.test.ts b/clients/validator/src/tests/sign/mixnetActions.test.ts new file mode 100644 index 0000000000..1b535f6f2d --- /dev/null +++ b/clients/validator/src/tests/sign/mixnetActions.test.ts @@ -0,0 +1,107 @@ +import expect from 'expect'; +import ValidatorClient from '../../'; + +const dotenv = require('dotenv'); + +dotenv.config(); + +// TODO: implement for QA with .env for mnemonics +describe('Mixnet actions', () => { + let client: ValidatorClient; + + beforeEach(async () => { + client = await ValidatorClient.connect( + process.env.mnemonic || '', + process.env.rpcAddress || '', + process.env.validatorAddress || '', + process.env.prefix || '', + process.env.mixnetContractAddress || '', + process.env.vestingContractAddress || '', + process.env.denom || '', + ); + }); + + it('can send tokens', async () => { + const res = await client.send(client.address, [{ amount: '10000000', denom: 'unym' }]); + expect(res.transactionHash).toBeDefined(); + }).timeout(10000); + + it.skip('can delegate tokens', async () => { + const [_, secondMixnode] = await client.getActiveMixnodes(); + + if (secondMixnode) { + const res = await client.delegateToMixNode( + secondMixnode.bond_information.mix_id, + { + amount: '15000000', + denom: 'unym', + }, + { gas: '1000000', amount: [{ amount: '100000', denom: 'unym' }] }, + ); + expect(res.transactionHash).toBeDefined(); + } + }).timeout(10000); + + // Need to provide a mix id that can be undelegated from + it.skip('can undelegate from a mixnode', async () => { + const mixId = 8; + const res = await client.undelegateFromMixNode(mixId); + expect(res.transactionHash).toBeDefined(); + }); + + it.skip('Can unbond a mixnode', async () => { + const res = await client.unbondMixNode(); + expect(res.transactionHash).toBeDefined(); + }).timeout(10000); + + it.skip('Can bond a mixnode', async () => { + const res = await client.bondMixNode( + { + identity_key: '3P6pAcF2p3pYMqWdpHqhbavu3ifyaBs5Qw5UmmCGwimx', + sphinx_key: 'GQMQKwUThaggatA6oZteSWTsCQoUfmLtamJ7o9YkP9aE', + host: '109.74.195.67', + mix_port: 1789, + verloc_port: 1790, + http_api_port: 8000, + version: '1.1.4', + }, + '3rXWCQBUj5JQB3UBUkZcXhCk9Zh3cjduMF8aFHPTG7KTkkhZzDJTNmE2p2Ph1g6iQW5vRGTpQzjXF33WDwvhzHk6', + { profit_margin_percent: '0.1', interval_operating_cost: { amount: '40', denom: 'nym' } }, + { amount: '100_000_000', denom: 'unym' }, + { gas: '1000000', amount: [{ amount: '100000', denom: 'unym' }] }, + ); + expect(res.transactionHash).toBeDefined(); + }).timeout(10000); + + it.skip('can unbond a gateway', async () => { + const res = await client.unbondGateway(); + expect(res.transactionHash).toBeDefined(); + }); + + it.skip('can bond a gateway', async () => { + const res = await client.bondGateway( + { + identity_key: '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL', + sphinx_key: 'G65Fwc2JNAotuHQFqmDKhQNQL5rn3r9pupUdmxMygNUZ', + host: '151.236.220.82', + version: '1.1.4', + mix_port: 1789, + clients_port: 9000, + location: 'Cuba', + }, + '3ipSJksWHehZm1YfuH5Ahtg7b22NnrP9hEs6iEDXfUS5uiUhpWmCjGR3b3NDHuxeGjpZYJNYJ52D8WCPK5ZR7Szj', + { amount: '100_000_000', denom: 'unym' }, + ); + expect(res.transactionHash).toBeDefined(); + }); + + it.skip('can update contract state params', async () => { + const res = await client.updateContractStateParams({ + minimum_mixnode_pledge: '', + minimum_gateway_pledge: '', + mixnode_rewarded_set_size: 2, + mixnode_active_set_size: 2, + }); + expect(res.transactionHash).toBeDefined(); + }); +}); diff --git a/clients/validator/src/tsconfig.json b/clients/validator/src/tsconfig.json deleted file mode 100644 index 7990d8ef90..0000000000 --- a/clients/validator/src/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "esModuleInterop": true, - "strict": true, - "declaration": true, - "outDir": "./dist" - }, - "exclude": [ - "tests", - "dist", - "node_modules" - ] -} \ No newline at end of file diff --git a/clients/validator/src/types.ts b/clients/validator/src/types.ts deleted file mode 100644 index 575702ec45..0000000000 --- a/clients/validator/src/types.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { Coin } from '@cosmjs/stargate'; - -// TODO: ideally we'd have re-exported those using that fancy crate that builds ts types from rust - -export type MixnetContractVersion = { - build_timestamp: string; - build_version: string; - commit_sha: string; - commit_timestamp: string; - commit_branch: string; - rustc_version: string; -}; - -export type PagedMixnodeResponse = { - nodes: MixNodeBond[]; - per_page: number; - start_next_after?: string; -}; - -export type PagedGatewayResponse = { - nodes: GatewayBond[]; - per_page: number; - start_next_after?: string; -}; - -export type MixOwnershipResponse = { - address: string; - mixnode?: MixNodeBond; -}; - -export type GatewayOwnershipResponse = { - address: string; - gateway?: GatewayBond; -}; - -export type ContractStateParams = { - // ideally I'd want to define those as `number` rather than `string`, but - // rust-side they are defined as Uint128 and that don't have - // native javascript representations and therefore are interpreted as strings after deserialization - minimum_mixnode_pledge: string; - minimum_gateway_pledge: string; - mixnode_rewarded_set_size: number; - mixnode_active_set_size: number; -}; - -export type LayerDistribution = { - gateways: number; - layer1: number; - layer2: number; - layer3: number; -}; - -export type Delegation = { - owner: string; - node_identity: string; - amount: Coin; - block_height: number; - proxy?: string; -}; - -export type PagedMixDelegationsResponse = { - delegations: Delegation[]; - start_next_after?: string; -}; - -export type PagedDelegatorDelegationsResponse = { - delegations: Delegation[]; - start_next_after?: string; -}; - -export type PagedAllDelegationsResponse = { - delegations: Delegation[]; - start_next_after?: [string, string]; -}; - -export type RewardingResult = { - operator_reward: string; - total_delegator_reward: string; -}; - -export type NodeRewardParams = { - period_reward_pool: string; - k: string; - reward_blockstamp: number; - circulating_supply: string; - uptime: string; - sybil_resistance_percent: number; -}; - -export type DelegatorRewardParams = { - node_reward_params: NodeRewardParams; - sigma: number; - profit_margin: number; - node_profit: number; -}; - -export type PendingDelegatorRewarding = { - running_results: RewardingResult; - next_start: string; - rewarding_params: DelegatorRewardParams; -}; - -export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding }; - -export type MixnodeRewardingStatusResponse = { - status?: RewardingStatus; -}; - -export enum Layer { - Gateway, - One, - Two, - Three, -} - -export type MixNodeBond = { - owner: string; - mix_node: MixNode; - layer: Layer; - bond_amount: Coin; - total_delegation: Coin; -}; - -export type MixNode = { - host: string; - mix_port: number; - verloc_port: number; - http_api_port: number; - sphinx_key: string; - identity_key: string; - version: string; - profit_margin_percent: number; -}; - -export type GatewayBond = { - owner: string; - gateway: Gateway; - - bond_amount: Coin; - total_delegation: Coin; -}; - -export type Gateway = { - host: string; - mix_port: number; - clients_port: number; - location: string; - sphinx_key: string; - identity_key: string; - version: string; -}; - -export type SendRequest = { - senderAddress: string; - recipientAddress: string; - transferAmount: readonly Coin[]; -}; diff --git a/clients/validator/src/types/global.d.ts b/clients/validator/src/types/global.d.ts new file mode 100644 index 0000000000..a6eb7c3521 --- /dev/null +++ b/clients/validator/src/types/global.d.ts @@ -0,0 +1,11 @@ +declare namespace NodeJS { + interface ProcessEnv { + rpcAddress: string; + validatorAddress: string; + prefix: string; + mixnetContractAddress: string; + vestingContractAddress: string; + denom: string; + mnemonic: string; + } +} diff --git a/clients/validator/src/types/shared.ts b/clients/validator/src/types/shared.ts new file mode 100644 index 0000000000..d8db5ad0e5 --- /dev/null +++ b/clients/validator/src/types/shared.ts @@ -0,0 +1,259 @@ +import { JsonObject } from '@cosmjs/cosmwasm-stargate'; +import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient'; +import { + Account, + Block, + Coin, + DeliverTxResponse, + IndexedTx, + SearchTxFilter, + SearchTxQuery, + SequenceResponse, +} from '@cosmjs/stargate'; +import { + MixNodeRewarding, + PagedMixNodeBondResponse, + PagedMixNodeDetailsResponse, + StakeSaturationResponse, + UnbondedMixnodeResponse, +} from '@nymproject/types'; + +export type MixnetContractVersion = { + build_timestamp: string; + build_version: string; + commit_sha: string; + commit_timestamp: string; + commit_branch: string; + rustc_version: string; +}; + +export type PagedMixnodeResponse = { + nodes: MixNodeBond[]; + per_page: number; + start_next_after?: string; +}; + +export type PagedGatewayResponse = { + nodes: GatewayBond[]; + per_page: number; + start_next_after?: string; +}; + +export type MixOwnershipResponse = { + address: string; + mixnode?: MixNodeBond; +}; + +export type GatewayOwnershipResponse = { + address: string; + gateway?: GatewayBond; +}; + +export type ContractStateParams = { + // ideally I'd want to define those as `number` rather than `string`, but + // rust-side they are defined as Uint128 and that don't have + // native javascript representations and therefore are interpreted as strings after deserialization + minimum_mixnode_pledge: string; + minimum_gateway_pledge: string; + mixnode_rewarded_set_size: number; + mixnode_active_set_size: number; +}; + +export type LayerDistribution = { + gateways: number; + layer1: number; + layer2: number; + layer3: number; +}; + +export type Delegation = { + owner: string; + node_identity: string; + amount: Coin; + block_height: number; + proxy?: string; +}; + +export type PagedMixDelegationsResponse = { + delegations: Delegation[]; + start_next_after?: string; +}; + +export type PagedDelegatorDelegationsResponse = { + delegations: Delegation[]; + start_next_after?: string; +}; + +export type PagedAllDelegationsResponse = { + delegations: Delegation[]; + start_next_after?: [string, string]; +}; + +export type RewardingResult = { + operator_reward: string; + total_delegator_reward: string; +}; + +export type NodeRewardParams = { + period_reward_pool: string; + k: string; + reward_blockstamp: number; + circulating_supply: string; + uptime: string; + sybil_resistance_percent: number; +}; + +export type DelegatorRewardParams = { + node_reward_params: NodeRewardParams; + sigma: number; + profit_margin: number; + node_profit: number; +}; + +export type PendingDelegatorRewarding = { + running_results: RewardingResult; + next_start: string; + rewarding_params: DelegatorRewardParams; +}; + +export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding }; + +export type MixnodeRewardingStatusResponse = { + status?: RewardingStatus; +}; + +export enum Layer { + Gateway, + One, + Two, + Three, +} + +export type MixNodeBond = { + owner: string; + mix_node: MixNode; + layer: Layer; + bond_amount: Coin; + total_delegation: Coin; +}; + +export type MixNode = { + host: string; + mix_port: number; + verloc_port: number; + http_api_port: number; + sphinx_key: string; + identity_key: string; + version: string; + profit_margin_percent: number; +}; + +export type GatewayBond = { + owner: string; + gateway: Gateway; + + bond_amount: Coin; + total_delegation: Coin; +}; + +export type Gateway = { + host: string; + mix_port: number; + clients_port: number; + location: string; + sphinx_key: string; + identity_key: string; + version: string; +}; + +export type SendRequest = { + senderAddress: string; + recipientAddress: string; + transferAmount: readonly Coin[]; +}; + +export interface SmartContractQuery { + queryContractSmart(address: string, queryMsg: Record): Promise; +} +export interface ICosmWasmQuery { + // methods exposed by `CosmWasmClient` + getChainId(): Promise; + getHeight(): Promise; + getAccount(searchAddress: string): Promise; + getSequence(address: string): Promise; + getBlock(height?: number): Promise; + getBalance(address: string, searchDenom: string): Promise; + getTx(id: string): Promise; + searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise; + disconnect(): void; + broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise; + getCodes(): Promise; + getCodeDetails(codeId: number): Promise; + getContracts(codeId: number): Promise; + getContract(address: string): Promise; + getContractCodeHistory(address: string): Promise; + queryContractRaw(address: string, key: Uint8Array): Promise; + queryContractSmart(address: string, queryMsg: Record): Promise; +} + +export interface INymdQuery { + // nym-specific implemented inside NymQuerier + getContractVersion(mixnetContractAddress: string): Promise; + getMixNodeBonds( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise; + getMixNodesDetailed( + mixnetContractAddress: string, + limit?: number, + startAfter?: string, + ): Promise; + getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise; + getOwnedMixnode(mixnetContractAddress: string, address: string): Promise; + ownsGateway(mixnetContractAddress: string, address: string): Promise; + getStateParams(mixnetContractAddress: string): Promise; + getAllNetworkDelegationsPaged( + mixnetContractAddress: string, + limit?: number, + startAfter?: [string, string], + ): Promise; + getMixNodeDelegationsPaged( + mixnetContractAddress: string, + mix_id: number, + limit?: number, + startAfter?: string, + ): Promise; + getDelegatorDelegationsPaged( + mixnetContractAddress: string, + delegator: string, + limit?: number, + startAfter?: string, + ): Promise; + getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise; + getLayerDistribution(mixnetContractAddress: string): Promise; + getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise; + getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise; + getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise; +} + +export interface IVestingQuerier { + getVestingContractVersion(mixnetContractAddress: string): Promise; +} + +export interface MappedCoin { + readonly denom: string; + readonly fractionalDigits: number; +} + +export interface CoinMap { + readonly [key: string]: MappedCoin; +} + +export interface ContractState { + owner: string; + rewarding_validator_address: string; + vesting_contract_address: string; + rewarding_denom: string; + params: ContractStateParams; +} diff --git a/clients/validator/tests/query/balance.test.ts b/clients/validator/tests/query/balance.test.ts deleted file mode 100644 index 254e54a745..0000000000 --- a/clients/validator/tests/query/balance.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import ValidatorClient from '../../dist'; -import expect from 'expect'; - -describe('Query: balances', () => { - it('can query for an account balance', async () => { - const client = await ValidatorClient.connectForQuery( - 'https://rpc.nymtech.net/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym'); - const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy'); - expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0); - }).timeout(5000); -}) diff --git a/clients/validator/tests/sign/send.test.ts b/clients/validator/tests/sign/send.test.ts deleted file mode 100644 index 197d5bd4ef..0000000000 --- a/clients/validator/tests/sign/send.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import ValidatorClient from '../../dist'; -import expect from 'expect'; - -// TODO: implement for QA with .env for mnemonics -// describe('Sign: send', () => { -// it('can send tokens', async () => { -// const client = await ValidatorClient.connect( -// '', -// 'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym'); -// await client.send('') -// const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy'); -// expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0); -// }).timeout(5000); -// }) \ No newline at end of file diff --git a/clients/validator/tsconfig.json b/clients/validator/tsconfig.json index ec8e7c1fb0..0a2ce0f074 100644 --- a/clients/validator/tsconfig.json +++ b/clients/validator/tsconfig.json @@ -1,23 +1,22 @@ { - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "esModuleInterop": true, - "strict": true, - "declaration": true, - "outDir": "./dist", - "skipLibCheck": true - }, - "typedocOptions": { - "entryPoints": [ - "src/index.ts" - ], - "out": "docs" - }, - "exclude": [ - "dist", - "examples", - "tests", - "node_modules" - ] -} \ No newline at end of file + "compilerOptions": { + "outDir": "./dist/nym-validator-client", + "module": "ES2020", + "target": "es2021", + "allowJs": false, + "strict": true, + "lib": ["es2021", "dom", "dom.iterable", "esnext"], + "moduleResolution": "node", + "skipDefaultLibCheck": true, + "esModuleInterop": true, + "declaration": true, + "skipLibCheck": true, + "noImplicitAny": true, + "typeRoots": ["./src/types/global.d.ts"] + }, + "typedocOptions": { + "entryPoints": ["./src/index.ts"], + "out": "docs" + }, + "exclude": ["dist", "./src/tests/**/*", "node_module"] +} diff --git a/clients/validator/tsconfig.test.json b/clients/validator/tsconfig.test.json new file mode 100644 index 0000000000..7048c297c8 --- /dev/null +++ b/clients/validator/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS" + } +} diff --git a/clients/webassembly/Cargo.toml b/clients/webassembly/Cargo.toml index cd60eb7dfa..190cc8e8d9 100644 --- a/clients/webassembly/Cargo.toml +++ b/clients/webassembly/Cargo.toml @@ -15,31 +15,30 @@ crate-type = ["cdylib", "rlib"] [features] default = ["console_error_panic_hook"] offline-test = [] -coconut = ["coconut-interface", "credentials", "gateway-client/coconut"] [dependencies] futures = "0.3" js-sys = "0.3" rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +anyhow = "1.0" serde-wasm-bindgen = "0.4" -tokio = { version = "1.21.2", features = ["sync"] } +tokio = { version = "1.24.1", features = ["sync"] } url = "2.2" wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4" # internal client-core = { path = "../client-core", default-features = false, features = ["wasm"] } -client-connections = { path = "../../common/client-connections" } -coconut-interface = { path = "../../common/coconut-interface", optional = true } -credentials = { path = "../../common/credentials", optional = true } -crypto = { path = "../../common/crypto" } -nymsphinx = { path = "../../common/nymsphinx" } -topology = { path = "../../common/topology" } -gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] } +nym-coconut-interface = { path = "../../common/coconut-interface" } +nym-credentials = { path = "../../common/credentials" } +nym-crypto = { path = "../../common/crypto" } +nym-sphinx = { path = "../../common/nymsphinx" } +gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] } validator-client = { path = "../../common/client-libs/validator-client", default-features = false } wasm-utils = { path = "../../common/wasm-utils" } -task = { path = "../../common/task" } +nym-task = { path = "../../common/task" } # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for diff --git a/clients/webassembly/README.md b/clients/webassembly/README.md index d2bb4966cc..6f290c1506 100644 --- a/clients/webassembly/README.md +++ b/clients/webassembly/README.md @@ -16,11 +16,7 @@ They should be implemented soon. You can build your applications, but don't rely ## Using it -See the [Nym docs](https://nymtech.net/docs). - -### Demo - -There's a demo web application in the `js-example` folder. To run it, first make sure you've got a recent `npm` installed, then follow the instructions in its README. +See the [SDK directory](../../sdk/typescript/examples) for examples on how to use it and the NPM packages available. ## Developing @@ -36,8 +32,13 @@ To be clear, this is not something that most JS developers need to worry about, ### Packaging -If you're a Nym platform developer who's made changes to the Rust (or JS) files and wants to re-publish the package to NPM, here's how you do it: +If you're a Nym platform developer who's made changes to the Rust files and wants to re-publish the package to NPM, here's how you do it: 1. bump version numbers as necessary for SemVer -2. `wasm-pack build --scope nymproject` builds the wasm binaries into the `pkg` directory (not in source control) -3. `cd pkg && npm publish --access=public` will publish your changed package to NPM +2. go the `sdk/typescript` directory (off the project root) +3. run: +``` +yarn +yarn build +yarn publish +``` \ No newline at end of file diff --git a/clients/webassembly/js-example/.bin/create-wasm-app.js b/clients/webassembly/js-example/.bin/create-wasm-app.js deleted file mode 100644 index 2f42973da2..0000000000 --- a/clients/webassembly/js-example/.bin/create-wasm-app.js +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env node - -const { spawn } = require("child_process"); -const fs = require("fs"); - -let folderName = '.'; - -if (process.argv.length >= 3) { - folderName = process.argv[2]; - if (!fs.existsSync(folderName)) { - fs.mkdirSync(folderName); - } -} - -const clone = spawn("git", ["clone", "https://github.com/rustwasm/create-wasm-app.git", folderName]); - -clone.on("close", code => { - if (code !== 0) { - console.error("cloning the template failed!") - process.exit(code); - } else { - console.log("🦀 Rust + 🕸 Wasm = ❤"); - } -}); diff --git a/clients/webassembly/js-example/.gitignore b/clients/webassembly/js-example/.gitignore deleted file mode 100644 index f06235c460..0000000000 --- a/clients/webassembly/js-example/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist diff --git a/clients/webassembly/js-example/.travis.yml b/clients/webassembly/js-example/.travis.yml deleted file mode 100644 index 04d40b4d7e..0000000000 --- a/clients/webassembly/js-example/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -node_js: "10" - -script: - - ./node_modules/.bin/webpack diff --git a/clients/webassembly/js-example/LICENSE-APACHE b/clients/webassembly/js-example/LICENSE-APACHE deleted file mode 100644 index 11069edd79..0000000000 --- a/clients/webassembly/js-example/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - 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/clients/webassembly/js-example/README.md b/clients/webassembly/js-example/README.md deleted file mode 100644 index f694466032..0000000000 --- a/clients/webassembly/js-example/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Nym Sphinx Wasm Demo - -This example application demonstrates how to use WebAssembly to create Sphinx packets, in the browser, and forward them to a Nym gateway. - -## 🚴 Usage - -Build the WASM package for bundling: - -``` -wasm-pack build --scope nymproject --target no-modules -``` - -in the `clients/webassembly` directory (one up). - -Start the webpack dev server: - -``` -npm install # set up dependencies -npm run start # starts a web server at http://localhost:8001 -``` - -Check your dev console for output. - -### Rebuild after Rust source changes - -Install `wasm-pack`. Instruction are at the [Rust WASM tutorial](https://rustwasm.github.io/docs/book/game-of-life/hello-world.html). - -`wasm-pack build --scope nymproject --target no-modules` in the `clients/webassembly` directory (one up) will rebuild the wasm package if you make changes to the Rust source. That will be automatically picked up (and reloaded, if need be) by the npm dev server. \ No newline at end of file diff --git a/clients/webassembly/js-example/bootstrap.js b/clients/webassembly/js-example/bootstrap.js deleted file mode 100644 index f1e71036b6..0000000000 --- a/clients/webassembly/js-example/bootstrap.js +++ /dev/null @@ -1,5 +0,0 @@ -// A dependency graph that contains any wasm must all be imported -// asynchronously. This `bootstrap.js` file does the single async import, so -// that no one else needs to worry about it again. -import('./index.js') - .catch(e => console.error('Error importing `index.js`:', e)); diff --git a/clients/webassembly/js-example/index.html b/clients/webassembly/js-example/index.html deleted file mode 100644 index fe167a99e5..0000000000 --- a/clients/webassembly/js-example/index.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - Nym WebAssembly Demo - - - -

- -

- -

- -

-

- -

-

- -

- -

Send messages from your browser, through the mixnet, and to the recipient using the "send" button.

-

Sent messages show in blue, received - messages show in green.

- -
-

- -

- - - - \ No newline at end of file diff --git a/clients/webassembly/js-example/index.js b/clients/webassembly/js-example/index.js deleted file mode 100644 index f0890bf12e..0000000000 --- a/clients/webassembly/js-example/index.js +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2020-2022 Nym Technologies SA -// -// 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. - -class WebWorkerClient { - worker = null; - - constructor() { - this.worker = new Worker('./worker.js'); - - this.worker.onmessage = (ev) => { - if (ev.data && ev.data.kind) { - switch (ev.data.kind) { - case 'Ready': - const { selfAddress } = ev.data.args; - displaySenderAddress(selfAddress); - break; - case 'ReceiveMessage': - const { message } = ev.data.args; - displayReceived(message); - break; - } - } - }; - } - - sendMessage = (message, recipient) => { - if (!this.worker) { - console.error('Could not send message because worker does not exist'); - return; - } - - this.worker.postMessage({ - kind: 'SendMessage', - args: { - message, recipient, - }, - }); - }; -} - -let client = null; - -async function main() { - client = new WebWorkerClient(); - - const sendButton = document.querySelector('#send-button'); - sendButton.onclick = function() { - sendMessageTo(); - }; -} - -/** - * Create a Sphinx packet and send it to the mixnet through the gateway node. - * - * Message and recipient are taken from the values in the user interface. - * - */ -async function sendMessageTo() { - const message = document.getElementById('message').value; - const recipient = document.getElementById('recipient').value; - - await client.sendMessage(message, recipient); - displaySend(message); -} - -/** - * Display messages that have been sent up the websocket. Colours them blue. - * - * @param {string} message - */ -function displaySend(message) { - let timestamp = new Date().toISOString().substr(11, 12); - - let sendDiv = document.createElement('div'); - let paragraph = document.createElement('p'); - paragraph.setAttribute('style', 'color: blue'); - let paragraphContent = document.createTextNode(timestamp + ' sent >>> ' + message); - paragraph.appendChild(paragraphContent); - - sendDiv.appendChild(paragraph); - document.getElementById('output').appendChild(sendDiv); -} - -/** - * Display received text messages in the browser. Colour them green. - * - * @param {Uint8Array} raw - */ -function displayReceived(raw, sender_tag) { - const content = new TextDecoder().decode(raw); - if (sender_tag !== undefined) { - console.log("this message also contained some surbs from", sender_tag) - } - - let timestamp = new Date().toISOString().substr(11, 12); - let receivedDiv = document.createElement('div'); - let paragraph = document.createElement('p'); - paragraph.setAttribute('style', 'color: green'); - let paragraphContent = document.createTextNode(timestamp + ' received >>> ' + content); - // let paragraphContent = document.createTextNode(timestamp + " received >>> " + content + ((replySurb != null) ? "Reply SURB was attached here (but we can't do anything with it yet" : " (NO REPLY-SURB AVAILABLE)")) - paragraph.appendChild(paragraphContent); - receivedDiv.appendChild(paragraph); - document.getElementById('output').appendChild(receivedDiv); -} - - -/** - * Display the nymClient's sender address in the user interface - * - * @param {String} address - */ -function displaySenderAddress(address) { - document.getElementById('sender').value = address; -} - -// Let's get started! -main(); \ No newline at end of file diff --git a/clients/webassembly/js-example/package-lock.json b/clients/webassembly/js-example/package-lock.json deleted file mode 100644 index ea8023655b..0000000000 --- a/clients/webassembly/js-example/package-lock.json +++ /dev/null @@ -1,6604 +0,0 @@ -{ - "name": "create-wasm-app", - "version": "0.1.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "create-wasm-app", - "version": "0.1.0", - "license": "Apache-2.0", - "dependencies": { - "@nymproject/nym-client-wasm": "file:../pkg" - }, - "bin": { - "create-wasm-app": ".bin/create-wasm-app.js" - }, - "devDependencies": { - "copy-webpack-plugin": "^10.2.4", - "hello-wasm-pack": "^0.1.0", - "webpack": "^5.70.0", - "webpack-cli": "^4.9.2", - "webpack-dev-server": "^4.7.4" - } - }, - "../pkg": { - "name": "@nymproject/nym-client-wasm", - "version": "1.1.0", - "license": "Apache-2.0" - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nymproject/nym-client-wasm": { - "resolved": "../pkg", - "link": true - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", - "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/eslint": { - "version": "8.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "4.17.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.28", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/@types/http-proxy": { - "version": "1.17.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.10", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "17.0.23", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/retry": { - "version": "0.12.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.13.10", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.5.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "envinfo": "^7.7.3" - }, - "peerDependencies": { - "webpack-cli": "4.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "1.6.1", - "dev": true, - "license": "MIT", - "peerDependencies": { - "webpack-cli": "4.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/accepts": { - "version": "1.3.7", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.7.0", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/array-flatten": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/array-union": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", - "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/async": { - "version": "2.6.4", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/body-parser": { - "version": "1.19.0", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/bonjour": { - "version": "3.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.20.2", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001317", - "electron-to-chromium": "^1.4.84", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/buffer-indexof": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001320", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/colorette": { - "version": "2.0.16", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/connect-history-api-fallback": { - "version": "1.6.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/copy-webpack-plugin": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", - "integrity": "sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "^3.2.7", - "glob-parent": "^6.0.1", - "globby": "^12.0.2", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - }, - "engines": { - "node": ">= 12.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/deep-equal": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/del": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del/node_modules/array-union": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/del/node_modules/globby": { - "version": "11.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/destroy": { - "version": "1.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, - "license": "MIT" - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true, - "license": "MIT" - }, - "node_modules/dns-packet": { - "version": "1.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/dns-txt": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-indexof": "^1.0.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.4.92", - "dev": true, - "license": "ISC" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.9.2", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true, - "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true, - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/express": { - "version": "4.17.1", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.2.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.12", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/follow-redirects": { - "version": "1.14.9", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-monkey": { - "version": "1.0.3", - "dev": true, - "license": "Unlicense" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "dev": true, - "license": "ISC", - "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" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/globby": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", - "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^3.0.1", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.7", - "ignore": "^5.1.9", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.9", - "dev": true, - "license": "ISC" - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true, - "license": "MIT" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hello-wasm-pack": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/hello-wasm-pack/-/hello-wasm-pack-0.1.0.tgz", - "integrity": "sha512-3hx0GDkDLf/a9ThCMV2qG4mwza8N/MCtm8aeFFc/cdBCL2zMJ1kW1wjNl7xPqD1lz8Yl5+uhnc/cpui4dLwz/w==", - "dev": true - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/html-entities": { - "version": "2.3.2", - "dev": true, - "license": "MIT" - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "1.7.2", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true, - "license": "ISC" - }, - "node_modules/http-parser-js": { - "version": "0.5.3", - "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/ip": { - "version": "1.1.5", - "dev": true, - "license": "MIT" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arguments": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "dev": true, - "license": "MIT" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.4.1", - "dev": true, - "license": "Unlicense", - "dependencies": { - "fs-monkey": "1.0.3" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "dev": true, - "license": "MIT" - }, - "node_modules/mkdirp": { - "version": "0.5.5", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/multicast-dns": { - "version": "6.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/multicast-dns-service-types": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-forge": { - "version": "1.3.0", - "dev": true, - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-is": { - "version": "1.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true, - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/retry": "^0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/portfinder": { - "version": "1.0.28", - "dev": true, - "license": "MIT", - "dependencies": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/portfinder/node_modules/debug": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/portfinder/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.7.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "^1.9.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true, - "license": "MIT" - }, - "node_modules/selfsigned": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "node-forge": "^1.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.17.1", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-static": { - "version": "1.14.1", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sockjs": { - "version": "0.3.21", - "dev": true, - "license": "MIT", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/spdy-transport/node_modules/debug": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/spdy-transport/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/spdy-transport/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/spdy/node_modules/debug": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/spdy/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/strip-ansi": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.12.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.7.3", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true, - "license": "MIT" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "3.4.0", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/watchpack": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/webpack": { - "version": "5.70.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "4.9.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.1", - "@webpack-cli/info": "^1.4.1", - "@webpack-cli/serve": "^1.6.1", - "colorette": "^2.0.14", - "commander": "^7.0.0", - "execa": "^5.0.0", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "webpack": "4.x.x || 5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "@webpack-cli/migrate": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.1", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-server": { - "version": "4.7.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.2.2", - "ansi-html-community": "^0.0.8", - "bonjour": "^3.5.0", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "default-gateway": "^6.0.3", - "del": "^6.0.0", - "express": "^4.17.1", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.0", - "ipaddr.js": "^2.0.1", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "portfinder": "^1.0.28", - "schema-utils": "^4.0.0", - "selfsigned": "^2.0.0", - "serve-index": "^1.9.1", - "sockjs": "^0.3.21", - "spdy": "^4.0.2", - "strip-ansi": "^7.0.0", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.4.2" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 12.13.0" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - }, - "dependencies": { - "@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@nymproject/nym-client-wasm": { - "version": "file:../pkg" - }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/connect-history-api-fallback": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", - "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", - "dev": true, - "requires": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "@types/eslint": { - "version": "8.4.1", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.3", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - }, - "@types/express": { - "version": "4.17.13", - "dev": true, - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.28", - "dev": true, - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/http-proxy": { - "version": "1.17.8", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/json-schema": { - "version": "7.0.10", - "dev": true - }, - "@types/mime": { - "version": "1.3.2", - "dev": true - }, - "@types/node": { - "version": "17.0.23", - "dev": true - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "@types/retry": { - "version": "0.12.1", - "dev": true - }, - "@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", - "dev": true, - "requires": { - "@types/express": "*" - } - }, - "@types/serve-static": { - "version": "1.13.10", - "dev": true, - "requires": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/ws": { - "version": "8.5.3", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webpack-cli/configtest": { - "version": "1.1.1", - "dev": true, - "requires": {} - }, - "@webpack-cli/info": { - "version": "1.4.1", - "dev": true, - "requires": { - "envinfo": "^7.7.3" - } - }, - "@webpack-cli/serve": { - "version": "1.6.1", - "dev": true, - "requires": {} - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "accepts": { - "version": "1.3.7", - "dev": true, - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "8.7.0", - "dev": true - }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} - }, - "aggregate-error": { - "version": "3.1.0", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "requires": { - "ajv": "^8.0.0" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true - }, - "ansi-regex": { - "version": "6.0.1", - "dev": true - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "array-flatten": { - "version": "2.1.2", - "dev": true - }, - "array-union": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", - "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", - "dev": true - }, - "async": { - "version": "2.6.4", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "body-parser": { - "version": "1.19.0", - "dev": true, - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "bytes": { - "version": "3.1.0", - "dev": true - } - } - }, - "bonjour": { - "version": "3.5.0", - "dev": true, - "requires": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.20.2", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001317", - "electron-to-chromium": "^1.4.84", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "buffer-indexof": { - "version": "1.1.1", - "dev": true - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "caniuse-lite": { - "version": "1.0.30001320", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "clean-stack": { - "version": "2.2.0", - "dev": true - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "colorette": { - "version": "2.0.16", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "connect-history-api-fallback": { - "version": "1.6.0", - "dev": true - }, - "content-disposition": { - "version": "0.5.3", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "cookie": { - "version": "0.4.0", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true - }, - "copy-webpack-plugin": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", - "integrity": "sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==", - "dev": true, - "requires": { - "fast-glob": "^3.2.7", - "glob-parent": "^6.0.1", - "globby": "^12.0.2", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - } - }, - "core-util-is": { - "version": "1.0.2", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-equal": { - "version": "1.1.1", - "dev": true, - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, - "requires": { - "execa": "^5.0.0" - } - }, - "define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "del": { - "version": "6.0.0", - "dev": true, - "requires": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "dependencies": { - "array-union": { - "version": "2.1.0", - "dev": true - }, - "globby": { - "version": "11.1.0", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "slash": { - "version": "3.0.0", - "dev": true - } - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true - }, - "destroy": { - "version": "1.0.4", - "dev": true - }, - "detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, - "dns-packet": { - "version": "1.3.4", - "dev": true, - "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "dns-txt": { - "version": "2.0.2", - "dev": true, - "requires": { - "buffer-indexof": "^1.0.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.92", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.9.2", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true - }, - "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "express": { - "version": "4.17.1", - "dev": true, - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.11", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fastest-levenshtein": { - "version": "1.0.12", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.1.2", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "follow-redirects": { - "version": "1.14.9", - "dev": true - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true - }, - "fs-monkey": { - "version": "1.0.3", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "7.2.0", - "dev": true, - "requires": { - "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" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "globby": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", - "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", - "dev": true, - "requires": { - "array-union": "^3.0.1", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.7", - "ignore": "^5.1.9", - "merge2": "^1.4.1", - "slash": "^4.0.0" - } - }, - "graceful-fs": { - "version": "4.2.9", - "dev": true - }, - "handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "dev": true - }, - "hello-wasm-pack": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/hello-wasm-pack/-/hello-wasm-pack-0.1.0.tgz", - "integrity": "sha512-3hx0GDkDLf/a9ThCMV2qG4mwza8N/MCtm8aeFFc/cdBCL2zMJ1kW1wjNl7xPqD1lz8Yl5+uhnc/cpui4dLwz/w==", - "dev": true - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "html-entities": { - "version": "2.3.2", - "dev": true - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true - }, - "http-errors": { - "version": "1.7.2", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - } - } - }, - "http-parser-js": { - "version": "0.5.3", - "dev": true - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-middleware": { - "version": "2.0.4", - "dev": true, - "requires": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "indent-string": { - "version": "4.0.0", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "dev": true - }, - "ip": { - "version": "1.1.5", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true - }, - "is-arguments": { - "version": "1.1.0", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.8.1", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.4", - "dev": true - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-cwd": { - "version": "2.2.0", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "dev": true - }, - "is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.3", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "json-parse-better-errors": { - "version": "1.0.2", - "dev": true - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "loader-runner": { - "version": "4.2.0", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true - }, - "memfs": { - "version": "3.4.1", - "dev": true, - "requires": { - "fs-monkey": "1.0.3" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "multicast-dns": { - "version": "6.2.3", - "dev": true, - "requires": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - } - }, - "multicast-dns-service-types": { - "version": "1.1.0", - "dev": true - }, - "negotiator": { - "version": "0.6.2", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node-forge": { - "version": "1.3.0", - "dev": true - }, - "node-releases": { - "version": "2.0.2", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "object-is": { - "version": "1.1.5", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "dev": true - }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", - "dev": true, - "requires": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-map": { - "version": "4.0.0", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-retry": { - "version": "4.6.1", - "dev": true, - "requires": { - "@types/retry": "^0.12.0", - "retry": "^0.13.1" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "portfinder": { - "version": "1.0.28", - "dev": true, - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "dev": true - } - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "proxy-addr": { - "version": "2.0.7", - "dev": true, - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.7.0", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true - }, - "raw-body": { - "version": "2.4.0", - "dev": true, - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "dependencies": { - "bytes": { - "version": "3.1.0", - "dev": true - } - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "requires": { - "resolve": "^1.9.0" - } - }, - "regexp.prototype.flags": { - "version": "1.3.1", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "resolve": { - "version": "1.22.0", - "dev": true, - "requires": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - } - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true - }, - "selfsigned": { - "version": "2.0.0", - "dev": true, - "requires": { - "node-forge": "^1.2.0" - } - }, - "send": { - "version": "0.17.1", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "ms": { - "version": "2.1.1", - "dev": true - } - } - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - } - } - }, - "serve-static": { - "version": "1.14.1", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "setprototypeof": { - "version": "1.1.1", - "dev": true - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true - }, - "sockjs": { - "version": "0.3.21", - "dev": true, - "requires": { - "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", - "websocket-driver": "^0.7.4" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "7.0.1", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true - }, - "terser": { - "version": "5.12.1", - "dev": true, - "requires": { - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.3.1", - "dev": true, - "requires": { - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.0", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true - }, - "uuid": { - "version": "3.4.0", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true - }, - "watchpack": { - "version": "2.3.1", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "requires": { - "minimalistic-assert": "^1.0.0" - } - }, - "webpack": { - "version": "5.70.0", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", - "webpack-sources": "^3.2.3" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "webpack-cli": { - "version": "4.9.2", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.1", - "@webpack-cli/info": "^1.4.1", - "@webpack-cli/serve": "^1.6.1", - "colorette": "^2.0.14", - "commander": "^7.0.0", - "execa": "^5.0.0", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "webpack-merge": "^5.7.3" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - } - } - }, - "webpack-dev-middleware": { - "version": "5.3.1", - "dev": true, - "requires": { - "colorette": "^2.0.10", - "memfs": "^3.4.1", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - } - }, - "webpack-dev-server": { - "version": "4.7.4", - "dev": true, - "requires": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.2.2", - "ansi-html-community": "^0.0.8", - "bonjour": "^3.5.0", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "default-gateway": "^6.0.3", - "del": "^6.0.0", - "express": "^4.17.1", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.0", - "ipaddr.js": "^2.0.1", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "portfinder": "^1.0.28", - "schema-utils": "^4.0.0", - "selfsigned": "^2.0.0", - "serve-index": "^1.9.1", - "sockjs": "^0.3.21", - "spdy": "^4.0.2", - "strip-ansi": "^7.0.0", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.4.2" - }, - "dependencies": { - "ipaddr.js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", - "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, - "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true - }, - "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "ws": { - "version": "8.5.0", - "dev": true, - "requires": {} - } - } -} diff --git a/clients/webassembly/js-example/package.json b/clients/webassembly/js-example/package.json deleted file mode 100644 index a843e96700..0000000000 --- a/clients/webassembly/js-example/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "create-wasm-app", - "version": "0.1.0", - "description": "create an app to consume rust-generated wasm packages", - "main": "index.js", - "bin": { - "create-wasm-app": ".bin/create-wasm-app.js" - }, - "scripts": { - "build": "webpack --config webpack.config.js", - "start": "webpack-dev-server --port 8001" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/rustwasm/create-wasm-app.git" - }, - "keywords": [ - "webassembly", - "wasm", - "rust", - "webpack" - ], - "author": "Dave Hrycyszyn ", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/nymtech/nym/issues" - }, - "homepage": "https://nymtech.net/docs", - "devDependencies": { - "copy-webpack-plugin": "^10.2.4", - "hello-wasm-pack": "^0.1.0", - "webpack": "^5.70.0", - "webpack-cli": "^4.9.2", - "webpack-dev-server": "^4.7.4" - }, - "dependencies": { - "@nymproject/nym-client-wasm": "file:../pkg" - } -} \ No newline at end of file diff --git a/clients/webassembly/js-example/webpack.config.js b/clients/webassembly/js-example/webpack.config.js deleted file mode 100644 index e07304fee3..0000000000 --- a/clients/webassembly/js-example/webpack.config.js +++ /dev/null @@ -1,33 +0,0 @@ -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const path = require('path'); - -module.exports = { - performance: { - hints: false, - maxEntrypointSize: 512000, - maxAssetSize: 512000 - }, - entry: { - bootstrap: './bootstrap.js', - worker: './worker.js', - }, - output: { - path: path.resolve(__dirname, 'dist'), - filename: '[name].js', - }, - // mode: 'development', - mode: 'production', - plugins: [ - new CopyWebpackPlugin({ - patterns: [ - 'index.html', - { - from: 'node_modules/@nymproject/nym-client-wasm/*.(js|wasm)', - to: '[name][ext]', - }, - ], - }), - - ], - experiments: { syncWebAssembly: true }, -}; diff --git a/clients/webassembly/js-example/worker.js b/clients/webassembly/js-example/worker.js deleted file mode 100644 index da066d6ea2..0000000000 --- a/clients/webassembly/js-example/worker.js +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2020-2022 Nym Technologies SA -// -// 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. - -importScripts('nym_client_wasm.js'); - -console.log('Initializing worker'); - -// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts` -const { default_debug, NymClientBuilder, set_panic_hook, Config, GatewayEndpointConfig } = wasm_bindgen; - -let client = null; - -async function main() { - // load WASM package - await wasm_bindgen('nym_client_wasm_bg.wasm'); - - console.log('Loaded WASM'); - - // sets up better stack traces in case of in-rust panics - set_panic_hook(); - - // validator server we will use to get topology from - const validator = 'https://qwerty-validator-api.qa.nymte.ch/api'; - - const gatewayId = 'EVupP2tRUeZo5Y6RpBHAbm8kSntpgNyZNL6yCr7BDEoG'; - const gatewayOwner = 'n1rmlew3euapuq7rs4s4j9apv00whrsazr764kl7'; - const gatewayListener = 'ws://176.58.120.72:9000'; - const gatewayEndpoint = new GatewayEndpointConfig(gatewayId, gatewayOwner, gatewayListener) - - // only really useful if you want to adjust some settings like traffic rate - // (if not needed you can just pass a null) - const debug = default_debug(); - - debug.disable_main_poisson_packet_distribution = true; - debug.disable_loop_cover_traffic_stream = true; - debug.use_extended_packet_size = true; - // debug.average_packet_delay_ms = BigInt(10); - // debug.average_ack_delay_ms = BigInt(10); - // debug.ack_wait_addition_ms = BigInt(3000); - // debug.ack_wait_multiplier = 10; - - debug.topology_refresh_rate_ms = BigInt(60000) - - const config = new Config('my-awesome-wasm-client', validator, gatewayEndpoint, debug); - - const onMessageHandler = (message) => { - self.postMessage({ - kind: 'ReceiveMessage', - args: { - message, - }, - }); - }; - - console.log('Instantiating WASM client...'); - - let clientBuilder = new NymClientBuilder(config, onMessageHandler) - console.log('Web worker creating WASM client...'); - let local_client = await clientBuilder.start_client(); - console.log('WASM client running!'); - - const selfAddress = local_client.self_address(); - - // set the global (I guess we don't have to anymore?) - client = local_client; - - console.log(`Client address is ${selfAddress}`); - self.postMessage({ - kind: 'Ready', - args: { - selfAddress, - }, - }); - - // Set callback to handle messages passed to the worker. - self.onmessage = async event => { - if (event.data && event.data.kind) { - switch (event.data.kind) { - case 'SendMessage': { - const { message, recipient } = event.data.args; - let uint8Array = new TextEncoder().encode(message); - await client.send_regular_message(uint8Array, recipient); - } - } - } - }; -} - -// Let's get started! -main(); \ No newline at end of file diff --git a/clients/webassembly/src/binary_message_helper.rs b/clients/webassembly/src/binary_message_helper.rs deleted file mode 100644 index 5032a87f5c..0000000000 --- a/clients/webassembly/src/binary_message_helper.rs +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use serde::{Deserialize, Serialize}; -use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; - -#[wasm_bindgen(typescript_custom_section)] -const TS_DEFS: &'static str = r#" -export interface BinaryMessage { - kind: number, - payload: Uint8Array; - headers: string, -} - -export interface StringMessage { - kind: number, - payload: string; -} -"#; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(typescript_type = "BinaryMessage")] - pub type IBinaryMessage; - #[wasm_bindgen(typescript_type = "StringMessage")] - pub type IStringMessage; -} - -#[derive(Serialize, Deserialize)] -pub struct BinaryMessage { - pub kind: u8, - pub payload: Vec, - pub headers: String, -} - -#[derive(Serialize, Deserialize)] -pub struct StringMessage { - pub kind: u8, - pub payload: String, -} - -/// Create a new binary message with a user-specified `kind`. -#[wasm_bindgen] -pub fn create_binary_message(kind: u8, payload: Vec) -> Vec { - create_binary_message_with_headers(kind, payload, "".to_string()) -} - -/// Create a new message with a UTF-8 encoded string `payload` and a user-specified `kind`. -#[wasm_bindgen] -pub fn create_binary_message_from_string(kind: u8, payload: String) -> Vec { - create_binary_message_with_headers(kind, payload.as_bytes().to_vec(), "".to_string()) -} - -/// Create a new binary message with a user-specified `kind`, and `headers` as a string. -#[wasm_bindgen] -pub fn create_binary_message_with_headers(kind: u8, payload: Vec, headers: String) -> Vec { - let headers = headers.as_bytes().to_vec(); - let size = (headers.len() as u64).to_be_bytes().to_vec(); - vec![vec![kind], size, headers, payload].concat() -} - -/// Parse the `kind` and byte array `payload` from a byte array -#[wasm_bindgen] -pub async fn parse_binary_message(message: Vec) -> Result { - if message.len() < 2 { - return Err(JsError::new( - "Could not parse message, as less than 2 bytes long", - )); - } - - let (kind, _headers, payload) = parse_binary_payload(&message); - - Ok(serde_wasm_bindgen::to_value(&BinaryMessage { - kind, - payload: payload.to_vec(), - headers: "".to_string(), - }) - .unwrap() - .unchecked_into::()) -} - -/// Parse the `kind` and byte array `payload` from a byte array with headers -#[wasm_bindgen] -pub async fn parse_binary_message_with_headers( - message: Vec, -) -> Result { - if message.len() < 2 { - return Err(JsError::new( - "Could not parse message, as less than 2 bytes long", - )); - } - - let (kind, headers, payload) = parse_binary_payload(&message); - - Ok(serde_wasm_bindgen::to_value(&BinaryMessage { - kind, - payload: payload.to_vec(), - headers, - }) - .unwrap() - .unchecked_into::()) -} - -/// Parse the `kind` and UTF-8 string `payload` from a byte array with headers -#[wasm_bindgen] -pub async fn parse_string_message_with_headers( - message: Vec, -) -> Result { - if message.len() < 2 { - return Err(JsError::new( - "Could not parse message, as less than 2 bytes long", - )); - } - - let (kind, _headers, payload) = parse_binary_payload(&message); - let payload = String::from_utf8_lossy(payload).into_owned(); - - Ok( - serde_wasm_bindgen::to_value(&StringMessage { kind, payload }) - .unwrap() - .unchecked_into::(), - ) -} -pub(crate) fn parse_binary_payload(message: &[u8]) -> (u8, String, &[u8]) { - // 1st byte is the kind - let kind = message[0]; - - // then the size as u64 big endian - let mut size = [0u8; 8]; - size.clone_from_slice(&message[1..9]); - let size = u64::from_be_bytes(size) as usize; - - // then the headers - let headers = String::from_utf8_lossy(&message[9..9 + size]).into_owned(); - - // finally the payload - let payload = &message[9 + size..]; - - (kind, headers, payload) -} - -/// Parse the `kind` and UTF-8 string `payload` from a byte array -#[wasm_bindgen] -pub async fn parse_string_message(message: Vec) -> Result { - if message.len() < 2 { - return Err(JsError::new( - "Could not parse message, as less than 2 bytes long", - )); - } - - let kind = message[0]; - let payload = String::from_utf8_lossy(&message[1..]).into_owned(); - - Ok( - serde_wasm_bindgen::to_value(&StringMessage { kind, payload }) - .unwrap() - .unchecked_into::(), - ) -} - -#[cfg(test)] -mod tests { - use super::{create_binary_message_with_headers, parse_binary_payload}; - use wasm_bindgen_test::*; - - #[wasm_bindgen_test] - fn test_binary_with_headers() { - let message_as_bytes = create_binary_message_with_headers( - 42u8, - vec![0u8, 1u8, 2u8], - "test headers".to_string(), - ); - - // calculate header size - let headers = "test headers".as_bytes().to_vec(); - let size = headers.len(); - - // the expected size - let expected_size = 12; - assert_eq!(size, expected_size); - - assert_eq!(message_as_bytes[0], 42u8); - assert_eq!(message_as_bytes[1..9], 12u64.to_be_bytes()); - assert_eq!( - message_as_bytes[9 + expected_size..9 + expected_size + 3], - vec![0u8, 1u8, 2u8] - ); - - let res = parse_binary_payload(&message_as_bytes); - - assert_eq!(res.0, 42u8); - assert_eq!(res.1, "test headers".to_string()); - assert_eq!(res.2, vec![0u8, 1u8, 2u8]); - } - - #[wasm_bindgen_test] - fn test_binary_with_empty_headers() { - let message_as_bytes = - create_binary_message_with_headers(42u8, vec![0u8, 1u8, 2u8], "".to_string()); - - let expected_size = 0; - - assert_eq!(message_as_bytes[0], 42u8); - assert_eq!(message_as_bytes[1..9], 0u64.to_be_bytes()); - assert_eq!( - message_as_bytes[9 + expected_size..9 + expected_size + 3], - vec![0u8, 1u8, 2u8] - ); - - let res = parse_binary_payload(&message_as_bytes); - - assert_eq!(res.0, 42u8); - assert_eq!(res.1, "".to_string()); - assert_eq!(res.2, vec![0u8, 1u8, 2u8]); - } -} diff --git a/clients/webassembly/src/client/config.rs b/clients/webassembly/src/client/config.rs index 4740a02b2a..b69769936c 100644 --- a/clients/webassembly/src/client/config.rs +++ b/clients/webassembly/src/client/config.rs @@ -17,7 +17,7 @@ pub struct Config { /// ID specifies the human readable ID of this particular client. pub(crate) id: String, - pub(crate) validator_api_url: Url, + pub(crate) nym_api_url: Url, pub(crate) disabled_credentials_mode: bool, @@ -38,7 +38,7 @@ impl Config { ) -> Self { Config { id, - validator_api_url: validator_server + nym_api_url: validator_server .parse() .expect("provided url was malformed"), disabled_credentials_mode: true, @@ -125,7 +125,11 @@ pub struct Debug { /// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking /// for more even though in theory they wouldn't need to. - pub maximum_reply_surb_waiting_period_ms: u64, + pub maximum_reply_surb_rerequest_waiting_period_ms: u64, + + /// Defines maximum amount of time the client is going to wait for reply surbs before + /// deciding it's never going to get them and would drop all pending messages + pub maximum_reply_surb_drop_waiting_period_ms: u64, /// Defines maximum amount of time given reply surb is going to be valid for. /// This is going to be superseded by key rotation once implemented. @@ -168,8 +172,11 @@ impl From for ConfigDebug { minimum_reply_surb_request_size: debug.minimum_reply_surb_request_size, maximum_reply_surb_request_size: debug.maximum_reply_surb_request_size, maximum_allowed_reply_surb_request_size: debug.maximum_allowed_reply_surb_request_size, - maximum_reply_surb_waiting_period: Duration::from_millis( - debug.maximum_reply_surb_waiting_period_ms, + maximum_reply_surb_rerequest_waiting_period: Duration::from_millis( + debug.maximum_reply_surb_rerequest_waiting_period_ms, + ), + maximum_reply_surb_drop_waiting_period: Duration::from_millis( + debug.maximum_reply_surb_drop_waiting_period_ms, ), maximum_reply_surb_age: Duration::from_millis(debug.maximum_reply_surb_age_ms), maximum_reply_key_age: Duration::from_millis(debug.maximum_reply_key_age_ms), @@ -200,8 +207,11 @@ impl From for Debug { minimum_reply_surb_request_size: debug.minimum_reply_surb_request_size, maximum_reply_surb_request_size: debug.maximum_reply_surb_request_size, maximum_allowed_reply_surb_request_size: debug.maximum_allowed_reply_surb_request_size, - maximum_reply_surb_waiting_period_ms: debug - .maximum_reply_surb_waiting_period + maximum_reply_surb_rerequest_waiting_period_ms: debug + .maximum_reply_surb_rerequest_waiting_period + .as_millis() as u64, + maximum_reply_surb_drop_waiting_period_ms: debug + .maximum_reply_surb_drop_waiting_period .as_millis() as u64, maximum_reply_surb_age_ms: debug.maximum_reply_surb_age.as_millis() as u64, maximum_reply_key_age_ms: debug.maximum_reply_key_age.as_millis() as u64, diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs index 9c3aefe8c6..dd45c28bb8 100644 --- a/clients/webassembly/src/client/mod.rs +++ b/clients/webassembly/src/client/mod.rs @@ -4,17 +4,20 @@ use self::config::Config; use crate::client::helpers::InputSender; use crate::client::response_pusher::ResponsePusher; -use client_connections::TransmissionLane; -use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput}; +use client_core::client::base_client::{ + BaseClientBuilder, ClientInput, ClientOutput, CredentialsToggle, +}; use client_core::client::replies::reply_storage::browser_backend; use client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager}; use gateway_client::bandwidth::BandwidthController; +use gateway_client::wasm_mockups::SigningNyxdClient; use js_sys::Promise; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_task::connections::TransmissionLane; +use nym_task::TaskManager; use rand::rngs::OsRng; use std::sync::Arc; -use task::ShutdownNotifier; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; use wasm_utils::{console_error, console_log}; @@ -30,7 +33,7 @@ pub struct NymClient { // even though we don't use graceful shutdowns, other components rely on existence of this struct // and if it's dropped, everything will start going offline - _shutdown: ShutdownNotifier, + _task_manager: TaskManager, } #[wasm_bindgen] @@ -45,7 +48,7 @@ pub struct NymClientBuilder { on_message: js_sys::Function, // unimplemented: - bandwidth_controller: Option, + bandwidth_controller: Option>, disabled_credentials: bool, } @@ -92,14 +95,20 @@ impl NymClientBuilder { future_to_promise(async move { console_log!("Starting the wasm client"); + let disabled_credentials = if self.disabled_credentials { + CredentialsToggle::Disabled + } else { + CredentialsToggle::Enabled + }; + let base_builder = BaseClientBuilder::new( &self.config.gateway_endpoint, &self.config.debug, self.key_manager, self.bandwidth_controller, self.reply_surb_storage_backend, - self.disabled_credentials, - vec![self.config.validator_api_url.clone()], + disabled_credentials, + vec![self.config.nym_api_url.clone()], ); let self_address = base_builder.as_mix_recipient().to_string(); @@ -121,7 +130,7 @@ impl NymClientBuilder { Ok(JsValue::from(NymClient { self_address, client_input: Arc::new(client_input), - _shutdown: started_client.shutdown_notifier, + _task_manager: started_client.task_manager, })) }) } diff --git a/clients/webassembly/src/encoded_payload_helper.rs b/clients/webassembly/src/encoded_payload_helper.rs new file mode 100644 index 0000000000..84f463ec58 --- /dev/null +++ b/clients/webassembly/src/encoded_payload_helper.rs @@ -0,0 +1,238 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +#[wasm_bindgen(typescript_custom_section)] +const TS_DEFS: &'static str = r#" +export interface EncodedPayload { + mimeType: string, + payload: Uint8Array; + headers: string, +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "EncodedPayload")] + pub type IEncodedPayload; +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EncodedPayload { + pub mime_type: String, + pub payload: Vec, + pub headers: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EncodedPayloadMetadata { + pub mime_type: String, + pub headers: Option, +} + +/// Encode a payload +#[wasm_bindgen] +pub fn encode_payload(mime_type: String, payload: Vec) -> Result, JsValue> { + encode_payload_with_headers(mime_type, payload, None) +} + +/// Create a new binary message with a user-specified `kind`, and `headers` as a string. +#[wasm_bindgen] +pub fn encode_payload_with_headers( + mime_type: String, + payload: Vec, + headers: Option, +) -> Result, JsValue> { + match serde_json::to_string(&EncodedPayloadMetadata { mime_type, headers }) { + Ok(metadata) => { + let metadata = metadata.as_bytes().to_vec(); + let size = (metadata.len() as u64).to_be_bytes().to_vec(); + Ok(vec![size, metadata, payload].concat()) + } + Err(e) => Err(JsValue::from(JsError::new( + format!("Could not encode message: {}", e).as_str(), + ))), + } +} + +/// Parse the `kind` and byte array `payload` from a byte array +#[wasm_bindgen] +pub fn decode_payload(message: Vec) -> Result { + if message.len() < 8 { + return Err(JsValue::from(JsError::new( + "Could not parse message, as less than 8 bytes long", + ))); + } + + match parse_payload(&message) { + Ok((metadata, payload)) => Ok(serde_wasm_bindgen::to_value(&EncodedPayload { + mime_type: metadata.mime_type, + payload: payload.to_vec(), + headers: metadata.headers, + }) + .unwrap() + .unchecked_into::()), + Err(e) => Err(JsValue::from(JsError::new( + format!("Could not parse message: {}", e).as_str(), + ))), + } +} + +pub(crate) fn parse_payload(message: &[u8]) -> anyhow::Result<(EncodedPayloadMetadata, &[u8])> { + // 1st 8 bytes are the size (as u64 big endian) + let mut size = [0u8; 8]; + size.clone_from_slice(&message[0..8]); + let size = u64::from_be_bytes(size) as usize; + + // then the metadata + let metadata = String::from_utf8_lossy(&message[8..8 + size]).into_owned(); + let metadata: EncodedPayloadMetadata = serde_json::from_str(metadata.as_str())?; + + // finally the payload + let payload = &message[8 + size..]; + + Ok((metadata, payload)) +} + +/// Try parse a UTF-8 string from an array of bytes +#[wasm_bindgen] +pub fn parse_utf8_string(payload: Vec) -> String { + String::from_utf8_lossy(&payload).into_owned() +} + +/// Converts a UTF-8 string into an array of bytes +/// +/// This method is provided as a replacement for the mess of `atob` +/// (https://developer.mozilla.org/en-US/docs/Web/API/atob) helpers provided by browsers and NodeJS. +/// +/// Feel free to use `atob` if you know you won't have problems with polyfills or encoding issues. +#[wasm_bindgen] +pub fn utf8_string_to_byte_array(message: String) -> Vec { + message.into_bytes() +} + +#[cfg(test)] +mod tests { + use super::*; + use wasm_bindgen_test::*; + + #[wasm_bindgen_test] + async fn test_encode_payload_with_headers() { + let message_as_bytes = encode_payload_with_headers( + "text/plain".to_string(), + vec![0u8, 1u8, 2u8], + Some("test headers".to_string()), + ) + .unwrap(); + + // the expected message size + let size = message_as_bytes.len(); + let expected_size = 61; + assert_eq!(size, expected_size); + + let expected_header_size = 50usize; + assert_eq!( + message_as_bytes[0..8], + (expected_header_size as u64).to_be_bytes() + ); + + assert_eq!( + message_as_bytes[8 + expected_header_size..8 + expected_header_size + 3], + vec![0u8, 1u8, 2u8] + ); + + let res = parse_payload(&message_as_bytes).unwrap(); + + assert_eq!(res.0.mime_type, "text/plain"); + assert_eq!(res.0.headers.unwrap(), "test headers".to_string()); + assert_eq!(res.1, vec![0u8, 1u8, 2u8]); + } + + #[wasm_bindgen_test] + async fn test_encode_payload_with_empty_headers() { + let message_as_bytes = + encode_payload_with_headers("text/plain".to_string(), vec![0u8, 1u8, 2u8], None) + .unwrap(); + + // the expected message size + let size = message_as_bytes.len(); + let expected_size = 51; + assert_eq!(size, expected_size); + + let expected_header_size = 40usize; + assert_eq!( + message_as_bytes[0..8], + (expected_header_size as u64).to_be_bytes() + ); + + assert_eq!( + message_as_bytes[8 + expected_header_size..8 + expected_header_size + 3], + vec![0u8, 1u8, 2u8] + ); + let res = parse_payload(&message_as_bytes).unwrap(); + + assert_eq!(res.0.mime_type, "text/plain"); + assert_eq!(res.0.headers, None); + assert_eq!(res.1, vec![0u8, 1u8, 2u8]); + } + + #[wasm_bindgen_test] + async fn test_encode_payload_with_empty_headers_and_empty_mime_type() { + let message_as_bytes = + encode_payload_with_headers("".to_string(), vec![0u8, 1u8, 2u8], None).unwrap(); + + // the expected message size + let size = message_as_bytes.len(); + let expected_size = 41; + assert_eq!(size, expected_size); + + let expected_header_size = 30usize; + assert_eq!( + message_as_bytes[0..8], + (expected_header_size as u64).to_be_bytes() + ); + + assert_eq!( + message_as_bytes[8 + expected_header_size..8 + expected_header_size + 3], + vec![0u8, 1u8, 2u8] + ); + let res = parse_payload(&message_as_bytes).unwrap(); + + assert_eq!(res.0.mime_type, ""); + assert_eq!(res.0.headers, None); + assert_eq!(res.1, vec![0u8, 1u8, 2u8]); + } + + #[wasm_bindgen_test] + async fn test_encode_payload_with_all_empty() { + let empty: Vec = vec![]; + let message_as_bytes = + encode_payload_with_headers("".to_string(), empty.clone(), None).unwrap(); + + // the expected message size + let size = message_as_bytes.len(); + let expected_size = 38; + assert_eq!(size, expected_size); + + let expected_header_size = 30usize; + assert_eq!( + message_as_bytes[0..8], + (expected_header_size as u64).to_be_bytes() + ); + + assert_eq!( + message_as_bytes[8 + expected_header_size..8 + expected_header_size], + empty + ); + let res = parse_payload(&message_as_bytes).unwrap(); + + assert_eq!(res.0.mime_type, ""); + assert_eq!(res.0.headers, None); + assert_eq!(res.1, empty); + } +} diff --git a/clients/webassembly/src/gateway_selector.rs b/clients/webassembly/src/gateway_selector.rs index 77f5a5c6f7..d92ac987c3 100644 --- a/clients/webassembly/src/gateway_selector.rs +++ b/clients/webassembly/src/gateway_selector.rs @@ -6,7 +6,7 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub async fn get_gateway(api_server: String, preferred: Option) -> GatewayEndpointConfig { - let validator_client = validator_client::client::ApiClient::new(api_server.parse().unwrap()); + let validator_client = validator_client::client::NymApiClient::new(api_server.parse().unwrap()); let gateways = match validator_client.get_cached_gateways().await { Err(err) => panic!("failed to obtain list of all gateways - {err}"), diff --git a/clients/webassembly/src/lib.rs b/clients/webassembly/src/lib.rs index 869b675829..3a97438249 100644 --- a/clients/webassembly/src/lib.rs +++ b/clients/webassembly/src/lib.rs @@ -3,12 +3,14 @@ use wasm_bindgen::prelude::*; -#[cfg(target_arch = "wasm32")] -pub mod binary_message_helper; #[cfg(target_arch = "wasm32")] mod client; #[cfg(target_arch = "wasm32")] +pub mod encoded_payload_helper; +#[cfg(target_arch = "wasm32")] pub mod gateway_selector; +#[cfg(target_arch = "wasm32")] +pub mod validation; #[wasm_bindgen] pub fn set_panic_hook() { diff --git a/clients/webassembly/src/validation.rs b/clients/webassembly/src/validation.rs new file mode 100644 index 0000000000..830a3d789a --- /dev/null +++ b/clients/webassembly/src/validation.rs @@ -0,0 +1,35 @@ +use nym_sphinx::addressing::clients::Recipient; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn validate_recipient(recipient: String) -> Result<(), JsError> { + match Recipient::try_from_base58_string(recipient) { + Ok(_) => Ok(()), + Err(e) => Err(JsError::new(format!("{}", e).as_str())), + } +} + +#[cfg(test)] +mod tests { + use super::validate_recipient; + use wasm_bindgen_test::*; + + #[wasm_bindgen_test] + fn test_recipient_validation_ok() { + let res = validate_recipient("DyQmXnst5NGGjzUZxRC5Bjs5bd7CBF3xMpsSmbRiizr2.GH6YTBP2NXU3AVqd8WjiTMVyeMjunXMEsp7gVCMEJqpD@336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9".to_string()); + assert!(res.is_ok()) + } + + #[wasm_bindgen_test] + fn test_recipient_validation_fails() { + assert!(validate_recipient(" DyQmXnst5NGGjzUZxRC5Bjs5bd7CBF3xMpsSmbRiizr2.GH6YTBP2NXU3AVqd8WjiTMVyeMjunXMEsp7gVCMEJqpD@336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9".to_string()).is_err()); + assert!(validate_recipient( + "DyQmXnst5NGGjzUZxRC5BjbRiizr2.GH6YTBP2NXU3AVqd8WD@336yuXAeGEgedRfqTJZQH1bHv1SjCZYarc9" + .to_string() + ) + .is_err()); + assert!(validate_recipient("🙀🙀🙀🙀".to_string()).is_err()); + assert!(validate_recipient("".to_string()).is_err()); + assert!(validate_recipient(" ".to_string()).is_err()); + } +} diff --git a/common/bandwidth-claim-contract/Cargo.toml b/common/bandwidth-claim-contract/Cargo.toml index e63e100d7a..bb9d6a85b3 100644 --- a/common/bandwidth-claim-contract/Cargo.toml +++ b/common/bandwidth-claim-contract/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bandwidth-claim-contract" +name = "nym-bandwidth-claim-contract" version = "0.1.0" edition = "2021" diff --git a/common/version-checker/.gitignore b/common/bin-common/.gitignore similarity index 100% rename from common/version-checker/.gitignore rename to common/bin-common/.gitignore diff --git a/common/bin-common/Cargo.toml b/common/bin-common/Cargo.toml new file mode 100644 index 0000000000..21f90d27f9 --- /dev/null +++ b/common/bin-common/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "nym-bin-common" +version = "0.3.0" +description = "Common code for nym binaries" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +[dependencies] +clap = { version = "4.0", features = ["derive"] } +clap_complete = "4.0" +clap_complete_fig = "4.0" +log = { workspace = true } +pretty_env_logger = "0.4.0" +semver = "0.11" +serde = { workspace = true, features = ["derive"], optional = true } + +[build-dependencies] +vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc", "cargo"] } + +[features] +default = [] diff --git a/common/bin-common/build.rs b/common/bin-common/build.rs new file mode 100644 index 0000000000..bd26c886d2 --- /dev/null +++ b/common/bin-common/build.rs @@ -0,0 +1,13 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use vergen::{vergen, Config}; + +fn main() { + let mut config = Config::default(); + if std::env::var("DOCS_RS").is_ok() { + // If we don't have access to git information, such as in a docs.rs build, don't error + *config.git_mut().skip_if_error_mut() = true; + } + vergen(config).expect("failed to extract build metadata"); +} diff --git a/common/bin-common/src/build_information/mod.rs b/common/bin-common/src/build_information/mod.rs new file mode 100644 index 0000000000..c5d7e7b4af --- /dev/null +++ b/common/bin-common/src/build_information/mod.rs @@ -0,0 +1,136 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +// TODO: at a later date this crate should probably also expose `ContractBuildInformation` +// and be used by our smart contracts + +#[derive(Debug)] +pub struct BinaryBuildInformation { + // VERGEN_BUILD_TIMESTAMP + /// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`. + pub build_timestamp: &'static str, + + // VERGEN_BUILD_SEMVER + /// Provides the build version, for example `0.1.0-9-g46f83e1`. + pub build_version: &'static str, + + // VERGEN_GIT_SHA + /// Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`. + pub commit_sha: &'static str, + + // VERGEN_GIT_COMMIT_TIMESTAMP + /// Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`. + pub commit_timestamp: &'static str, + + // VERGEN_GIT_BRANCH + /// Provides the name of the git branch that was used for the build, for example `master`. + pub commit_branch: &'static str, + + // VERGEN_RUSTC_SEMVER + /// Provides the rustc version that was used for the build, for example `1.52.0-nightly`. + pub rustc_version: &'static str, + + // VERGEN_RUSTC_CHANNEL + /// Provides the rustc channel that was used for the build, for example `nightly`. + pub rustc_channel: &'static str, + + // VERGEN_CARGO_PROFILE + /// Provides the cargo profile that was used for the build, for example `debug`. + pub cargo_profile: &'static str, +} + +impl BinaryBuildInformation { + // explicitly require the build_version to be passed as it's binary specific + pub const fn new(build_version: &'static str) -> Self { + BinaryBuildInformation { + build_timestamp: env!("VERGEN_BUILD_TIMESTAMP"), + build_version, + commit_sha: env!("VERGEN_GIT_SHA"), + commit_timestamp: env!("VERGEN_GIT_COMMIT_TIMESTAMP"), + commit_branch: env!("VERGEN_GIT_BRANCH"), + rustc_version: env!("VERGEN_RUSTC_SEMVER"), + rustc_channel: env!("VERGEN_RUSTC_CHANNEL"), + cargo_profile: env!("VERGEN_CARGO_PROFILE"), + } + } + + pub fn to_owned(&self) -> BinaryBuildInformationOwned { + BinaryBuildInformationOwned { + build_timestamp: self.build_timestamp.to_owned(), + build_version: self.build_version.to_owned(), + commit_sha: self.commit_sha.to_owned(), + commit_timestamp: self.commit_timestamp.to_owned(), + commit_branch: self.commit_branch.to_owned(), + rustc_version: self.rustc_version.to_owned(), + rustc_channel: self.rustc_channel.to_owned(), + cargo_profile: self.cargo_profile.to_owned(), + } + } + + pub fn pretty_print(&self) -> String { + format!( + r#" +{:<20}{} +{:<20}{} +{:<20}{} +{:<20}{} +{:<20}{} +{:<20}{} +{:<20}{} +{:<20}{} +"#, + "Build Timestamp:", + self.build_timestamp, + "Build Version:", + self.build_version, + "Commit SHA:", + self.commit_sha, + "Commit Date:", + self.commit_timestamp, + "Commit Branch:", + self.commit_branch, + "rustc Version:", + self.rustc_version, + "rustc Channel:", + self.rustc_channel, + "cargo Profile:", + self.cargo_profile, + ) + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BinaryBuildInformationOwned { + // VERGEN_BUILD_TIMESTAMP + /// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`. + pub build_timestamp: String, + + // VERGEN_BUILD_SEMVER + /// Provides the build version, for example `0.1.0-9-g46f83e1`. + pub build_version: String, + + // VERGEN_GIT_SHA + /// Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`. + pub commit_sha: String, + + // VERGEN_GIT_COMMIT_TIMESTAMP + /// Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`. + pub commit_timestamp: String, + + // VERGEN_GIT_BRANCH + /// Provides the name of the git branch that was used for the build, for example `master`. + pub commit_branch: String, + + // VERGEN_RUSTC_SEMVER + /// Provides the rustc version that was used for the build, for example `1.52.0-nightly`. + pub rustc_version: String, + + // VERGEN_RUSTC_CHANNEL + /// Provides the rustc channel that was used for the build, for example `nightly`. + pub rustc_channel: String, + + // VERGEN_CARGO_PROFILE + /// Provides the cargo profile that was used for the build, for example `debug`. + pub cargo_profile: String, +} diff --git a/common/completions/src/lib.rs b/common/bin-common/src/completions/mod.rs similarity index 92% rename from common/completions/src/lib.rs rename to common/bin-common/src/completions/mod.rs index 8526c18d4f..15344ec834 100644 --- a/common/completions/src/lib.rs +++ b/common/bin-common/src/completions/mod.rs @@ -1,5 +1,5 @@ use clap::builder::Command; -use clap::clap_derive::ArgEnum; +use clap::clap_derive::ValueEnum; use clap::Args; use clap_complete::generator::generate; use clap_complete::Shell as ClapShell; @@ -14,7 +14,7 @@ pub fn fig_generate(command: &mut Command, name: &str) { ) } -#[derive(ArgEnum, Copy, Clone)] +#[derive(ValueEnum, Copy, Clone)] pub enum Shell { Bash, Elvish, @@ -25,7 +25,7 @@ pub enum Shell { #[derive(Args, Copy, Clone)] pub struct ArgShell { - #[clap(arg_enum, value_name = "SHELL")] + #[clap(value_enum, value_name = "SHELL")] shell: Shell, } diff --git a/common/credentials/src/token/mod.rs b/common/bin-common/src/lib.rs similarity index 53% rename from common/credentials/src/token/mod.rs rename to common/bin-common/src/lib.rs index ac23bbe001..eed7b4f998 100644 --- a/common/credentials/src/token/mod.rs +++ b/common/bin-common/src/lib.rs @@ -1,4 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub mod bandwidth; +pub mod build_information; +pub mod completions; +pub mod logging; +pub mod version_checker; diff --git a/common/logging/src/lib.rs b/common/bin-common/src/logging/mod.rs similarity index 77% rename from common/logging/src/lib.rs rename to common/bin-common/src/logging/mod.rs index 6c026e9b90..27f7711e80 100644 --- a/common/logging/src/lib.rs +++ b/common/bin-common/src/logging/mod.rs @@ -23,3 +23,19 @@ pub fn setup_logging() { .filter_module("sled", log::LevelFilter::Warn) .init(); } + +pub fn banner(crate_name: &str, crate_version: &str) -> String { + format!( + r#" + + _ __ _ _ _ __ ___ + | '_ \| | | | '_ \ _ \ + | | | | |_| | | | | | | + |_| |_|\__, |_| |_| |_| + |___/ + + ({crate_name} - version {crate_version}) + + "# + ) +} diff --git a/common/version-checker/src/lib.rs b/common/bin-common/src/version_checker/mod.rs similarity index 100% rename from common/version-checker/src/lib.rs rename to common/bin-common/src/version_checker/mod.rs diff --git a/contracts/mixnet/.cargo-ok b/common/build-information/Cargo.toml similarity index 100% rename from contracts/mixnet/.cargo-ok rename to common/build-information/Cargo.toml diff --git a/common/client-connections/Cargo.toml b/common/client-connections/Cargo.toml deleted file mode 100644 index 7438be79ba..0000000000 --- a/common/client-connections/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "client-connections" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -futures = "0.3" -log = "0.4.17" diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml index 8f1de308d3..f630a24154 100644 --- a/common/client-libs/gateway-client/Cargo.toml +++ b/common/client-libs/gateway-client/Cargo.toml @@ -10,23 +10,25 @@ edition = "2021" # TODO: (for this and other crates), similarly to 'tokio', import only required "futures" modules rather than # the entire crate futures = "0.3" -log = "0.4" +log = { workspace = true } thiserror = "1.0" url = "2.2" rand = { version = "0.7.3", features = ["wasm-bindgen"] } -async-trait = { version = "0.1.51" } -tokio = { version = "1.21.2", features = ["macros"] } +async-trait = { workspace = true } +tokio = { version = "1.24.1", features = ["macros"] } # internal -coconut-interface = { path = "../../coconut-interface", optional = true } -credentials = { path = "../../credentials" } -crypto = { path = "../../crypto" } +nym-coconut-interface = { path = "../../coconut-interface" } +nym-credentials = { path = "../../credentials" } +nym-crypto = { path = "../../crypto" } gateway-requests = { path = "../../../gateway/gateway-requests" } -network-defaults = { path = "../../network-defaults" } -nymsphinx = { path = "../../nymsphinx" } -pemstore = { path = "../../pemstore" } -validator-client = { path = "../validator-client", optional = true } -task = { path = "../../task" } +nym-network-defaults = { path = "../../network-defaults" } +nym-sphinx = { path = "../../nymsphinx" } +nym-pemstore = { path = "../../pemstore" } +validator-client = { path = "../validator-client" } +nym-task = { path = "../../task" } +serde = { version = "1.0", features = ["derive"]} +mobile-storage = { path = "../../mobile-storage" } [dependencies.tungstenite] @@ -35,17 +37,17 @@ default-features = false # non-wasm-only dependencies [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] -version = "1.21.2" +version = "1.24.1" features = ["macros", "rt", "net", "sync", "time"] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream] -version = "0.1.9" +version = "0.1.11" features = ["net", "sync", "time"] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite] version = "0.14" -[target."cfg(not(target_arch = \"wasm32\"))".dependencies.credential-storage] +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-credential-storage] path = "../../credential-storage" # wasm-only dependencies @@ -78,5 +80,5 @@ features = ["js"] #url = "2.1" [features] -coconut = ["gateway-requests/coconut", "coconut-interface", "validator-client", "credentials/coconut"] wasm = [] +mobile = [] diff --git a/common/client-libs/gateway-client/src/bandwidth.rs b/common/client-libs/gateway-client/src/bandwidth.rs index d6d1cb04f0..5d1d0c50bf 100644 --- a/common/client-libs/gateway-client/src/bandwidth.rs +++ b/common/client-libs/gateway-client/src/bandwidth.rs @@ -4,75 +4,95 @@ use crate::error::GatewayClientError; #[cfg(target_arch = "wasm32")] -use crate::wasm_storage::Storage; +use crate::wasm_mockups::Storage; #[cfg(not(target_arch = "wasm32"))] -use credential_storage::storage::Storage; +#[cfg(not(feature = "mobile"))] +use nym_credential_storage::storage::Storage; -#[cfg(all(target_arch = "wasm32", feature = "coconut"))] -use crate::wasm_storage::StorageError; +#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "mobile")] +use mobile_storage::Storage; -#[cfg(all(not(target_arch = "wasm32"), feature = "coconut"))] -use credential_storage::error::StorageError; +#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "mobile")] +use mobile_storage::StorageError; -#[cfg(feature = "coconut")] +#[cfg(target_arch = "wasm32")] +use crate::wasm_mockups::StorageError; + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(feature = "mobile"))] +use nym_credential_storage::error::StorageError; + +#[cfg(target_arch = "wasm32")] +use crate::wasm_mockups::{Client, CosmWasmClient}; use std::str::FromStr; -#[cfg(feature = "coconut")] -use validator_client::client::CoconutApiClient; -#[cfg(feature = "coconut")] +#[cfg(not(target_arch = "wasm32"))] +use validator_client::{nyxd::CosmWasmClient, Client}; use { - coconut_interface::Base58, - credentials::coconut::{ + nym_coconut_interface::Base58, + nym_credentials::coconut::{ bandwidth::prepare_for_spending, utils::obtain_aggregate_verification_key, }, }; // TODO: make it nicer for wasm (I don't want to touch it for this experiment) #[cfg(target_arch = "wasm32")] -use crate::wasm_storage::PersistentStorage; +use crate::wasm_mockups::PersistentStorage; #[cfg(not(target_arch = "wasm32"))] -use credential_storage::PersistentStorage; +#[cfg(not(feature = "mobile"))] +use nym_credential_storage::PersistentStorage; + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "mobile")] +use mobile_storage::PersistentStorage; #[derive(Clone)] -pub struct BandwidthController { - #[allow(dead_code)] +#[allow(dead_code)] +pub struct BandwidthController { storage: St, - #[cfg(feature = "coconut")] - coconut_api_clients: Vec, + nyxd_client: Client, } -impl BandwidthController +impl BandwidthController where + C: CosmWasmClient + Sync + Send + Clone, St: Storage + Clone + 'static, { - #[cfg(feature = "coconut")] - pub fn new(storage: St, coconut_api_clients: Vec) -> Self { + pub fn new(storage: St, nyxd_client: Client) -> Self { BandwidthController { storage, - coconut_api_clients, + nyxd_client, } } - #[cfg(not(feature = "coconut"))] - pub fn new(storage: St) -> Result { - Ok(BandwidthController { storage }) - } - - #[cfg(feature = "coconut")] pub async fn prepare_coconut_credential( &self, - ) -> Result<(coconut_interface::Credential, i64), GatewayClientError> { - let verification_key = obtain_aggregate_verification_key(&self.coconut_api_clients).await?; + ) -> Result<(nym_coconut_interface::Credential, i64), GatewayClientError> { let bandwidth_credential = self.storage.get_next_coconut_credential().await?; 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 = - coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?; + nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?; let binding_number = - coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?; + nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?; let signature = - coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?; + nym_coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?; + let epoch_id = u64::from_str(&bandwidth_credential.epoch_id) + .map_err(|_| StorageError::InconsistentData)?; + + #[cfg(not(target_arch = "wasm32"))] + let coconut_api_clients = validator_client::CoconutApiClient::all_coconut_api_clients( + &self.nyxd_client, + epoch_id, + ) + .await + .expect("Could not query api clients"); + #[cfg(target_arch = "wasm32")] + let coconut_api_clients = vec![]; + let verification_key = obtain_aggregate_verification_key(&coconut_api_clients).await?; // the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff) Ok(( @@ -81,6 +101,7 @@ where voucher_info, serial_number, binding_number, + epoch_id, &signature, &verification_key, )?, @@ -88,7 +109,6 @@ where )) } - #[cfg(feature = "coconut")] pub async fn consume_credential(&self, id: i64) -> Result<(), GatewayClientError> { Ok(self.storage.consume_coconut_credential(id).await?) } diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index e1c2654b80..0919a81e85 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -9,32 +9,40 @@ pub use crate::packet_router::{ }; use crate::socket_state::{PartiallyDelegated, SocketState}; use crate::{cleanup_socket_message, try_decrypt_binary_message}; -use crypto::asymmetric::identity; use futures::{SinkExt, StreamExt}; use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; use gateway_requests::iv::IV; use gateway_requests::registration::handshake::{client_handshake, SharedKeys}; use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, PROTOCOL_VERSION}; use log::*; -use network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN}; -use nymsphinx::forwarding::packet::MixPacket; +use nym_coconut_interface::Credential; +use nym_crypto::asymmetric::identity; +use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN}; +use nym_sphinx::forwarding::packet::MixPacket; +use nym_task::TaskClient; use rand::rngs::OsRng; use std::convert::TryFrom; use std::sync::Arc; use std::time::Duration; -use task::ShutdownListener; use tungstenite::protocol::Message; -#[cfg(feature = "coconut")] -use coconut_interface::Credential; - -#[cfg(not(target_arch = "wasm32"))] -use credential_storage::PersistentStorage; #[cfg(not(target_arch = "wasm32"))] use tokio_tungstenite::connect_async; +#[cfg(not(target_arch = "wasm32"))] +use validator_client::nyxd::CosmWasmClient; + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(feature = "mobile"))] +use nym_credential_storage::PersistentStorage; + +#[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "mobile")] +use mobile_storage::PersistentStorage; #[cfg(target_arch = "wasm32")] -use crate::wasm_storage::PersistentStorage; +use crate::wasm_mockups::CosmWasmClient; +#[cfg(target_arch = "wasm32")] +use crate::wasm_mockups::PersistentStorage; #[cfg(target_arch = "wasm32")] use wasm_timer; #[cfg(target_arch = "wasm32")] @@ -43,19 +51,18 @@ use wasm_utils::websocket::JSWebsocket; const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10; const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5); -pub struct GatewayClient { +pub struct GatewayClient { authenticated: bool, disabled_credentials_mode: bool, bandwidth_remaining: i64, gateway_address: String, gateway_identity: identity::PublicKey, - gateway_owner: String, local_identity: Arc, shared_key: Option>, connection: SocketState, packet_router: PacketRouter, response_timeout_duration: Duration, - bandwidth_controller: Option>, + bandwidth_controller: Option>, // reconnection related variables /// Specifies whether client should try to reconnect to gateway on connection failure. @@ -67,23 +74,25 @@ pub struct GatewayClient { reconnection_backoff: Duration, /// Listen to shutdown messages. - shutdown: ShutdownListener, + shutdown: TaskClient, } -impl GatewayClient { +impl GatewayClient +where + C: CosmWasmClient + Sync + Send + Clone, +{ // TODO: put it all in a Config struct #[allow(clippy::too_many_arguments)] pub fn new( gateway_address: String, local_identity: Arc, gateway_identity: identity::PublicKey, - gateway_owner: String, shared_key: Option>, mixnet_message_sender: MixnetMessageSender, ack_sender: AcknowledgementSender, response_timeout_duration: Duration, - bandwidth_controller: Option>, - shutdown: ShutdownListener, + bandwidth_controller: Option>, + shutdown: TaskClient, ) -> Self { GatewayClient { authenticated: false, @@ -91,7 +100,6 @@ impl GatewayClient { bandwidth_remaining: 0, gateway_address, gateway_identity, - gateway_owner, local_identity, shared_key, connection: SocketState::NotConnected, @@ -125,7 +133,6 @@ impl GatewayClient { pub fn new_init( gateway_address: String, gateway_identity: identity::PublicKey, - gateway_owner: String, local_identity: Arc, response_timeout_duration: Duration, ) -> Self { @@ -135,16 +142,15 @@ impl GatewayClient { // perfectly fine here, because it's not meant to be used let (ack_tx, _) = mpsc::unbounded(); let (mix_tx, _) = mpsc::unbounded(); - let shutdown = ShutdownListener::dummy(); + let shutdown = TaskClient::dummy(); let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone()); - GatewayClient { + GatewayClient:: { authenticated: false, disabled_credentials_mode: true, bandwidth_remaining: 0, gateway_address, gateway_identity, - gateway_owner, local_identity, shared_key: None, connection: SocketState::NotConnected, @@ -305,11 +311,11 @@ impl GatewayClient { if let Some(shared_keys) = &self.shared_key { if let Some(plaintext) = try_decrypt_binary_message(bin_msg, shared_keys) { if let Err(err) = self.packet_router.route_received(vec![plaintext]) { - log::warn!("Route received failed: {:?}", err); + log::warn!("Route received failed: {err}"); } } } else if let Err(err) = self.packet_router.route_received(vec![bin_msg]) { - log::warn!("Route received failed: {:?}", err); + log::warn!("Route received failed: {err}"); } } Message::Text(txt_msg) => { @@ -435,6 +441,7 @@ impl GatewayClient { } debug_assert!(self.connection.is_available()); + log::trace!("Registering gateway"); // it's fine to instantiate it here as it's only used once (during authentication or registration) // and putting it into the GatewayClient struct would be a hassle @@ -534,7 +541,6 @@ impl GatewayClient { } } - #[cfg(feature = "coconut")] async fn claim_coconut_bandwidth( &mut self, credential: Credential, @@ -584,28 +590,21 @@ impl GatewayClient { return self.try_claim_testnet_bandwidth().await; } - let _gateway_owner = self.gateway_owner.clone(); - - #[cfg(feature = "coconut")] let (credential, credential_id) = self .bandwidth_controller .as_ref() .unwrap() .prepare_coconut_credential() .await?; - #[cfg(not(feature = "coconut"))] - return self.try_claim_testnet_bandwidth().await; - #[cfg(feature = "coconut")] - { - self.claim_coconut_bandwidth(credential).await?; - self.bandwidth_controller - .as_ref() - .unwrap() - .consume_credential(credential_id) - .await?; - Ok(()) - } + self.claim_coconut_bandwidth(credential).await?; + self.bandwidth_controller + .as_ref() + .unwrap() + .consume_credential(credential_id) + .await?; + + Ok(()) } fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 { diff --git a/common/client-libs/gateway-client/src/error.rs b/common/client-libs/gateway-client/src/error.rs index 8a8104658b..c83ee33e42 100644 --- a/common/client-libs/gateway-client/src/error.rs +++ b/common/client-libs/gateway-client/src/error.rs @@ -2,10 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 #[cfg(target_arch = "wasm32")] -use crate::wasm_storage::StorageError; -#[cfg(not(target_arch = "wasm32"))] -use credential_storage::error::StorageError; +use crate::wasm_mockups::StorageError; use gateway_requests::registration::handshake::error::HandshakeError; +#[cfg(feature = "mobile")] +#[cfg(not(target_arch = "wasm32"))] +use mobile_storage::StorageError; +#[cfg(not(feature = "mobile"))] +#[cfg(not(target_arch = "wasm32"))] +use nym_credential_storage::error::StorageError; use std::io; use thiserror::Error; use tungstenite::Error as WsError; @@ -26,9 +30,8 @@ pub enum GatewayClientError { #[error("There was a credential storage error - {0}")] CredentialStorageError(#[from] StorageError), - #[cfg(feature = "coconut")] #[error("Coconut error - {0}")] - CoconutError(#[from] coconut_interface::CoconutError), + CoconutError(#[from] nym_coconut_interface::CoconutError), // TODO: see if `JsValue` is a reasonable type for this #[cfg(target_arch = "wasm32")] @@ -45,7 +48,7 @@ pub enum GatewayClientError { NoBandwidthControllerAvailable, #[error("Credential error - {0}")] - CredentialError(#[from] credentials::error::Error), + CredentialError(#[from] nym_credentials::error::Error), #[error("Connection was abruptly closed")] ConnectionAbruptlyClosed, diff --git a/common/client-libs/gateway-client/src/lib.rs b/common/client-libs/gateway-client/src/lib.rs index ac209f0920..1dc95a71aa 100644 --- a/common/client-libs/gateway-client/src/lib.rs +++ b/common/client-libs/gateway-client/src/lib.rs @@ -17,7 +17,7 @@ pub mod error; pub mod packet_router; pub mod socket_state; #[cfg(target_arch = "wasm32")] -mod wasm_storage; +pub mod wasm_mockups; /// Helper method for reading from websocket stream. Helps to flatten the structure. pub(crate) fn cleanup_socket_message( diff --git a/common/client-libs/gateway-client/src/packet_router.rs b/common/client-libs/gateway-client/src/packet_router.rs index fd63077ee6..1d62893d9e 100644 --- a/common/client-libs/gateway-client/src/packet_router.rs +++ b/common/client-libs/gateway-client/src/packet_router.rs @@ -7,9 +7,9 @@ use crate::error::GatewayClientError; use futures::channel::mpsc; use log::*; -use nymsphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN; -use nymsphinx::params::packet_sizes::PacketSize; -use task::ShutdownListener; +use nym_sphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN; +use nym_sphinx::params::packet_sizes::PacketSize; +use nym_task::TaskClient; pub type MixnetMessageSender = mpsc::UnboundedSender>>; pub type MixnetMessageReceiver = mpsc::UnboundedReceiver>>; @@ -21,14 +21,14 @@ pub type AcknowledgementReceiver = mpsc::UnboundedReceiver>>; pub struct PacketRouter { ack_sender: AcknowledgementSender, mixnet_message_sender: MixnetMessageSender, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl PacketRouter { pub fn new( ack_sender: AcknowledgementSender, mixnet_message_sender: MixnetMessageSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { PacketRouter { ack_sender, @@ -74,7 +74,7 @@ impl PacketRouter { received_messages.push(received_packet); } else { // this can happen if other clients are not padding their messages - warn!("Received message of unexpected size. Probably from an outdated client... len: {}", received_packet.len()); + //warn!("Received message of unexpected size. Probably from an outdated client... len: {}", received_packet.len()); received_messages.push(received_packet); } } @@ -89,14 +89,14 @@ impl PacketRouter { } // This should never happen during ordinary operation the way it's currently used. // Abort to be on the safe side - panic!("Failed to send mixnet message: {:?}", err); + panic!("Failed to send mixnet message: {err}"); } } if !received_acks.is_empty() { trace!("routing acks"); - if let Err(e) = self.ack_sender.unbounded_send(received_acks) { - error!("failed to send ack: {:?}", e); + if let Err(err) = self.ack_sender.unbounded_send(received_acks) { + error!("failed to send ack: {err}"); }; } Ok(()) diff --git a/common/client-libs/gateway-client/src/socket_state.rs b/common/client-libs/gateway-client/src/socket_state.rs index aa96b9a4fa..007a1b1313 100644 --- a/common/client-libs/gateway-client/src/socket_state.rs +++ b/common/client-libs/gateway-client/src/socket_state.rs @@ -9,8 +9,8 @@ use futures::stream::{SplitSink, SplitStream}; use futures::{SinkExt, StreamExt}; use gateway_requests::registration::handshake::SharedKeys; use log::*; +use nym_task::TaskClient; use std::sync::Arc; -use task::ShutdownListener; use tungstenite::Message; #[cfg(not(target_arch = "wasm32"))] @@ -84,7 +84,7 @@ impl PartiallyDelegated { conn: WsConn, packet_router: PacketRouter, shared_key: Arc, - mut shutdown: ShutdownListener, + mut shutdown: TaskClient, ) -> Self { // when called for, it NEEDS TO yield back the stream so that we could merge it and // read control request responses. @@ -115,7 +115,7 @@ impl PartiallyDelegated { }; if let Err(err) = Self::route_socket_messages(ws_msgs, &mut packet_router, shared_key.as_ref()) { - log::warn!("Route socket messages failed: {:?}", err); + log::warn!("Route socket messages failed: {err}"); } } }; diff --git a/common/client-libs/gateway-client/src/wasm_storage.rs b/common/client-libs/gateway-client/src/wasm_mockups.rs similarity index 87% rename from common/client-libs/gateway-client/src/wasm_storage.rs rename to common/client-libs/gateway-client/src/wasm_mockups.rs index 644560b9ca..55b07e367c 100644 --- a/common/client-libs/gateway-client/src/wasm_storage.rs +++ b/common/client-libs/gateway-client/src/wasm_mockups.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use async_trait::async_trait; +use std::marker::PhantomData; use thiserror::Error; #[derive(Error, Debug)] @@ -14,6 +15,18 @@ pub enum StorageError { InconsistentData, } +pub trait CosmWasmClient {} + +#[derive(Clone)] +pub struct SigningNyxdClient {} + +impl CosmWasmClient for SigningNyxdClient {} + +#[derive(Clone)] +pub struct Client { + _phantom: PhantomData, +} + #[derive(Clone)] pub struct PersistentStorage {} @@ -24,6 +37,7 @@ pub struct CoconutCredential { pub serial_number: String, pub binding_number: String, pub signature: String, + pub epoch_id: String, } #[async_trait] diff --git a/common/client-libs/mixnet-client/Cargo.toml b/common/client-libs/mixnet-client/Cargo.toml index 1d8e6abd1e..adaa7b28f0 100644 --- a/common/client-libs/mixnet-client/Cargo.toml +++ b/common/client-libs/mixnet-client/Cargo.toml @@ -9,9 +9,10 @@ edition = "2021" [dependencies] futures = "0.3" tracing = "0.1.37" -log = "0.4.8" -tokio = { version = "1.21.2", features = ["time", "net", "rt"] } -tokio-util = { version = "0.7.3", features = ["codec"] } +log = { workspace = true } +tokio = { version = "1.24.1", features = ["time", "net", "rt"] } +tokio-util = { version = "0.7.4", features = ["codec"] } # internal -nymsphinx = {path = "../../nymsphinx" } +nym-sphinx = { path = "../../nymsphinx" } +nym-task = { path = "../../task" } diff --git a/common/client-libs/mixnet-client/src/client.rs b/common/client-libs/mixnet-client/src/client.rs index 7cc02cc15b..da6aecee60 100644 --- a/common/client-libs/mixnet-client/src/client.rs +++ b/common/client-libs/mixnet-client/src/client.rs @@ -4,11 +4,11 @@ use futures::channel::mpsc; use futures::StreamExt; use tracing::*; -use nymsphinx::framing::codec::SphinxCodec; -use nymsphinx::framing::packet::FramedSphinxPacket; -use nymsphinx::params::PacketMode; -use nymsphinx::params::packet_sizes::PacketSize; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; +use nym_sphinx::framing::codec::SphinxCodec; +use nym_sphinx::framing::packet::FramedSphinxPacket; +use nym_sphinx::params::PacketMode; +use nym_sphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; +use nym_sphinx::params::packet_sizes::PacketSize; use std::collections::HashMap; use std::io; use std::net::SocketAddr; @@ -123,7 +123,7 @@ impl Client { // We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care // about neither receiver nor the connection, it doesn't matter which one gets consumed if let Err(err) = receiver.map(Ok).forward(conn).await { - warn!("Failed to forward packets to {} - {:?}", address, err); + warn!("Failed to forward packets to {} - {err}", address); } debug!( diff --git a/common/client-libs/mixnet-client/src/forwarder.rs b/common/client-libs/mixnet-client/src/forwarder.rs index d92f6ad790..cefae1fc30 100644 --- a/common/client-libs/mixnet-client/src/forwarder.rs +++ b/common/client-libs/mixnet-client/src/forwarder.rs @@ -5,7 +5,7 @@ use crate::client::{Client, Config, SendWithoutResponse}; use futures::channel::mpsc; use futures::StreamExt; use log::*; -use nymsphinx::forwarding::packet::MixPacket; +use nym_sphinx::forwarding::packet::MixPacket; use std::time::Duration; pub type MixForwardingSender = mpsc::UnboundedSender; @@ -16,6 +16,7 @@ type MixForwardingReceiver = mpsc::UnboundedReceiver; pub struct PacketForwarder { mixnet_client: Client, packet_receiver: MixForwardingReceiver, + shutdown: nym_task::TaskClient, } impl PacketForwarder { @@ -25,6 +26,7 @@ impl PacketForwarder { initial_connection_timeout: Duration, maximum_connection_buffer_size: usize, use_legacy_version: bool, + shutdown: nym_task::TaskClient, ) -> (PacketForwarder, MixForwardingSender) { let client_config = Config::new( initial_reconnection_backoff, @@ -40,26 +42,35 @@ impl PacketForwarder { PacketForwarder { mixnet_client: Client::new(client_config), packet_receiver, + shutdown, }, packet_sender, ) } pub async fn run(&mut self) { - while let Some(mix_packet) = self.packet_receiver.next().await { - trace!("Going to forward packet to {:?}", mix_packet.next_hop()); + while !self.shutdown.is_shutdown() { + tokio::select! { + biased; + _ = self.shutdown.recv() => { + log::trace!("PacketForwarder: Received shutdown"); + } + Some(mix_packet) = self.packet_receiver.next() => { + trace!("Going to forward packet to {:?}", mix_packet.next_hop()); - let next_hop = mix_packet.next_hop(); - let packet_mode = mix_packet.packet_mode(); - let sphinx_packet = mix_packet.into_sphinx_packet(); - // we don't care about responses, we just want to fire packets - // as quickly as possible + let next_hop = mix_packet.next_hop(); + let packet_mode = mix_packet.packet_mode(); + let sphinx_packet = mix_packet.into_sphinx_packet(); + // we don't care about responses, we just want to fire packets + // as quickly as possible - if let Err(err) = - self.mixnet_client - .send_without_response(next_hop, sphinx_packet, packet_mode) - { - debug!("failed to forward the packet - {}", err) + if let Err(err) = + self.mixnet_client + .send_without_response(next_hop, sphinx_packet, packet_mode) + { + debug!("failed to forward the packet - {err}") + } + } } } } diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index ec842828e7..7673c3ab46 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -11,56 +11,61 @@ rust-version = "1.56" base64 = "0.13" colored = "2.0" -coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" } -contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" } -mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" } -vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" } -coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" } -multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } -vesting-contract = { path = "../../../contracts/vesting" } +nym-coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" } +nym-contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" } +nym-mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" } +nym-vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" } +nym-coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" } +nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } +nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" } +nym-vesting-contract = { path = "../../../contracts/vesting" } serde = { version = "1", features = ["derive"] } serde_json = "1" reqwest = { version = "0.11", features = ["json"] } thiserror = "1" -log = "0.4" +log = { workspace = true } url = { version = "2.2", features = ["serde"] } -tokio = { version = "1.21.2", features = ["sync", "time"] } +tokio = { version = "1.24.1", features = ["sync", "time"] } futures = "0.3" -coconut-interface = { path = "../../coconut-interface" } -network-defaults = { path = "../../network-defaults" } -validator-api-requests = { path = "../../../validator-api/validator-api-requests", features = ["coconut"] } +nym-coconut-interface = { path = "../../coconut-interface" } +nym-network-defaults = { path = "../../network-defaults" } +nym-api-requests = { path = "../../../nym-api/nym-api-requests" } +nym-execute = { path = "../../execute" } -# required for nymd-client +# required for nyxd-client # at some point it might be possible to make it wasm-compatible # perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated) -async-trait = { version = "0.1.51", optional = true } +async-trait = { workspace = true, optional = true } bip39 = { version = "1", features = ["rand"], optional = true } -config = { path = "../../config", optional = true } +nym-config = { path = "../../config", optional = true } cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true} -cw3 = { version = "0.13.4", optional = true } +cw3 = { workspace = true, optional = true } +cw4 = { workspace = true, optional = true } prost = { version = "0.10", default-features = false, optional = true } flate2 = { version = "1.0.20", optional = true } sha2 = { version = "0.9.5", optional = true } itertools = { version = "0.10", optional = true } -cosmwasm-std = { version = "1.0.0", optional = true } -execute = { path = "../../execute" } +cosmwasm-std = { workspace = true, optional = true } +zeroize = { version = "1.5.7", optional = true } [dev-dependencies] ts-rs = "6.1.2" [features] -nymd-client = [ +nyxd-client = [ "async-trait", "bip39", - "config", + "nym-config", "cosmrs", "cw3", + "cw4", "prost", "flate2", "sha2", "itertools", "cosmwasm-std", + "zeroize" ] generate-ts = [] diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index b5746ea06c..1aaa514b87 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -1,55 +1,55 @@ // Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::{validator_api, ValidatorClientError}; -use coconut_dkg_common::types::NodeIndex; -use coconut_interface::VerificationKey; -use mixnet_contract_common::mixnode::MixNodeDetails; -use mixnet_contract_common::MixId; -use mixnet_contract_common::{GatewayBond, IdentityKeyRef}; -use validator_api_requests::coconut::{ +use crate::{nym_api, ValidatorClientError}; +use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; -use validator_api_requests::models::{ +use nym_api_requests::models::{ GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, }; +use nym_coconut_dkg_common::types::NodeIndex; +use nym_coconut_interface::VerificationKey; +pub use nym_mixnet_contract_common::{mixnode::MixNodeDetails, GatewayBond, IdentityKeyRef, MixId}; -#[cfg(feature = "nymd-client")] -use crate::nymd::traits::{DkgQueryClient, MixnetQueryClient, MultisigQueryClient}; -#[cfg(feature = "nymd-client")] -use crate::nymd::{self, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient}; -#[cfg(feature = "nymd-client")] -use coconut_dkg_common::{ - dealer::ContractDealing, types::DealerDetails, verification_key::ContractVKShare, -}; -#[cfg(feature = "nymd-client")] -use coconut_interface::Base58; -#[cfg(feature = "nymd-client")] +#[cfg(feature = "nyxd-client")] +use crate::nyxd::traits::{DkgQueryClient, MixnetQueryClient, MultisigQueryClient}; +#[cfg(feature = "nyxd-client")] +use crate::nyxd::{self, CosmWasmClient, NyxdClient, QueryNyxdClient, SigningNyxdClient}; +#[cfg(feature = "nyxd-client")] use cw3::ProposalResponse; -#[cfg(feature = "nymd-client")] -use mixnet_contract_common::{ +#[cfg(feature = "nyxd-client")] +use nym_api_requests::models::MixNodeBondAnnotated; +#[cfg(feature = "nyxd-client")] +use nym_coconut_dkg_common::{ + dealer::ContractDealing, + types::{DealerDetails, EpochId}, + verification_key::ContractVKShare, +}; +#[cfg(feature = "nyxd-client")] +use nym_coconut_interface::Base58; +#[cfg(feature = "nyxd-client")] +use nym_mixnet_contract_common::{ families::{Family, FamilyHead}, mixnode::MixNodeBond, pending_events::{PendingEpochEvent, PendingIntervalEvent}, Delegation, IdentityKey, RewardedSetNodeStatus, UnbondedMixnode, }; -#[cfg(feature = "nymd-client")] -use network_defaults::NymNetworkDetails; -#[cfg(feature = "nymd-client")] +#[cfg(feature = "nyxd-client")] +use nym_network_defaults::NymNetworkDetails; +#[cfg(feature = "nyxd-client")] use std::str::FromStr; use url::Url; -#[cfg(feature = "nymd-client")] -use validator_api_requests::models::MixNodeBondAnnotated; -#[cfg(feature = "nymd-client")] +#[cfg(feature = "nyxd-client")] #[must_use] #[derive(Debug, Clone)] pub struct Config { api_url: Url, - nymd_url: Url, + nyxd_url: Url, - nymd_config: nymd::Config, + nyxd_config: nyxd::Config, mixnode_page_limit: Option, gateway_page_limit: Option, @@ -60,7 +60,7 @@ pub struct Config { proposals_page_limit: Option, } -#[cfg(feature = "nymd-client")] +#[cfg(feature = "nyxd-client")] impl Config { pub fn try_from_nym_network_details( details: &NymNetworkDetails, @@ -78,11 +78,11 @@ impl Config { Ok(Config { api_url: api_url.pop().unwrap(), - nymd_url: details.endpoints[0] - .nymd_url + nyxd_url: details.endpoints[0] + .nyxd_url .parse() .map_err(ValidatorClientError::MalformedUrlProvided)?, - nymd_config: nymd::Config::try_from_nym_network_details(details)?, + nyxd_config: nyxd::Config::try_from_nym_network_details(details)?, mixnode_page_limit: None, gateway_page_limit: None, mixnode_delegations_page_limit: None, @@ -95,14 +95,14 @@ impl Config { // TODO: this method shouldn't really exist as all information should be included immediately // via `from_nym_network_details`, but it's here for, you guessed it, legacy compatibility - pub fn with_urls(mut self, nymd_url: Url, api_url: Url) -> Self { - self.nymd_url = nymd_url; + pub fn with_urls(mut self, nyxd_url: Url, api_url: Url) -> Self { + self.nyxd_url = nyxd_url; self.api_url = api_url; self } - pub fn with_nymd_url(mut self, nymd_url: Url) -> Self { - self.nymd_url = nymd_url; + pub fn with_nyxd_url(mut self, nyxd_url: Url) -> Self { + self.nyxd_url = nyxd_url; self } @@ -127,12 +127,12 @@ impl Config { } } -#[cfg(feature = "nymd-client")] -pub struct Client { - // TODO: we really shouldn't be storing a mnemonic here, but removing it would be - // non-trivial amount of work and it's out of scope of the current branch - mnemonic: Option, - +#[cfg(feature = "nyxd-client")] +#[derive(Clone)] +pub struct Client { + // // TODO: we really shouldn't be storing a mnemonic here, but removing it would be + // // non-trivial amount of work and it's out of scope of the current branch + // mnemonic: Option, mixnode_page_limit: Option, gateway_page_limit: Option, mixnode_delegations_page_limit: Option, @@ -142,26 +142,25 @@ pub struct Client { proposals_page_limit: Option, // ideally they would have been read-only, but unfortunately rust doesn't have such features - pub validator_api: validator_api::Client, - pub nymd: NymdClient, + pub nym_api: nym_api::Client, + pub nyxd: NyxdClient, } -#[cfg(feature = "nymd-client")] -impl Client { +#[cfg(feature = "nyxd-client")] +impl Client { pub fn new_signing( config: Config, mnemonic: bip39::Mnemonic, - ) -> Result, ValidatorClientError> { - let validator_api_client = validator_api::Client::new(config.api_url.clone()); - let nymd_client = NymdClient::connect_with_mnemonic( - config.nymd_config.clone(), - config.nymd_url.as_str(), - mnemonic.clone(), + ) -> Result, ValidatorClientError> { + let nym_api_client = nym_api::Client::new(config.api_url.clone()); + let nyxd_client = NyxdClient::connect_with_mnemonic( + config.nyxd_config.clone(), + config.nyxd_url.as_str(), + mnemonic, None, )?; Ok(Client { - mnemonic: Some(mnemonic), mixnode_page_limit: config.mixnode_page_limit, gateway_page_limit: config.gateway_page_limit, mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, @@ -169,35 +168,29 @@ impl Client { dealers_page_limit: config.dealers_page_limit, verification_key_page_limit: config.verification_key_page_limit, proposals_page_limit: config.proposals_page_limit, - validator_api: validator_api_client, - nymd: nymd_client, + nym_api: nym_api_client, + nyxd: nyxd_client, }) } - pub fn change_nymd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> { - self.nymd = NymdClient::connect_with_mnemonic( - self.nymd.current_config().clone(), - new_endpoint.as_ref(), - self.mnemonic.clone().unwrap(), - None, - )?; + pub fn change_nyxd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> { + self.nyxd.change_endpoint(new_endpoint.as_ref())?; Ok(()) } - pub fn set_nymd_simulated_gas_multiplier(&mut self, multiplier: f32) { - self.nymd.set_simulated_gas_multiplier(multiplier) + pub fn set_nyxd_simulated_gas_multiplier(&mut self, multiplier: f32) { + self.nyxd.set_simulated_gas_multiplier(multiplier) } } -#[cfg(feature = "nymd-client")] -impl Client { - pub fn new_query(config: Config) -> Result, ValidatorClientError> { - let validator_api_client = validator_api::Client::new(config.api_url.clone()); - let nymd_client = - NymdClient::connect(config.nymd_config.clone(), config.nymd_url.as_str())?; +#[cfg(feature = "nyxd-client")] +impl Client { + pub fn new_query(config: Config) -> Result, ValidatorClientError> { + let nym_api_client = nym_api::Client::new(config.api_url.clone()); + let nyxd_client = + NyxdClient::connect(config.nyxd_config.clone(), config.nyxd_url.as_str())?; Ok(Client { - mnemonic: None, mixnode_page_limit: config.mixnode_page_limit, gateway_page_limit: config.gateway_page_limit, mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, @@ -205,30 +198,33 @@ impl Client { dealers_page_limit: config.dealers_page_limit, verification_key_page_limit: config.verification_key_page_limit, proposals_page_limit: config.proposals_page_limit, - validator_api: validator_api_client, - nymd: nymd_client, + nym_api: nym_api_client, + nyxd: nyxd_client, }) } - pub fn change_nymd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> { - self.nymd = NymdClient::connect(self.nymd.current_config().clone(), new_endpoint.as_ref())?; + pub fn change_nyxd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> { + self.nyxd = NyxdClient::connect(self.nyxd.current_config().clone(), new_endpoint.as_ref())?; Ok(()) } } -// nymd wrappers -#[cfg(feature = "nymd-client")] -impl Client { +// nyxd wrappers +#[cfg(feature = "nyxd-client")] +impl Client +where + C: Clone, +{ // use case: somebody initialised client without a contract in order to upload and initialise one // and now they want to actually use it without making new client pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmrs::AccountId) { - self.nymd + self.nyxd .set_mixnet_contract_address(mixnet_contract_address) } pub fn get_mixnet_contract_address(&self) -> cosmrs::AccountId { - self.nymd.mixnet_contract_address().clone() + self.nyxd.mixnet_contract_address().clone() } pub async fn get_all_node_families(&self) -> Result, ValidatorClientError> @@ -240,7 +236,7 @@ impl Client { loop { let paged_response = self - .nymd + .nyxd .get_all_node_families_paged(start_after.take(), None) .await?; families.extend(paged_response.families); @@ -266,7 +262,7 @@ impl Client { loop { let paged_response = self - .nymd + .nyxd .get_all_family_members_paged(start_after.take(), None) .await?; members.extend(paged_response.members); @@ -282,7 +278,7 @@ impl Client { } // basically handles paging for us - pub async fn get_all_nymd_rewarded_set_mixnodes( + pub async fn get_all_nyxd_rewarded_set_mixnodes( &self, ) -> Result, ValidatorClientError> where @@ -293,7 +289,7 @@ impl Client { loop { let mut paged_response = self - .nymd + .nyxd .get_rewarded_set_paged(start_after.take(), self.rewarded_set_page_limit) .await?; identities.append(&mut paged_response.nodes); @@ -308,7 +304,7 @@ impl Client { Ok(identities) } - pub async fn get_all_nymd_mixnode_bonds(&self) -> Result, ValidatorClientError> + pub async fn get_all_nyxd_mixnode_bonds(&self) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync + Send, { @@ -316,7 +312,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_mixnode_bonds_paged(self.mixnode_page_limit, start_after.take()) .await?; mixnodes.append(&mut paged_response.nodes); @@ -331,7 +327,7 @@ impl Client { Ok(mixnodes) } - pub async fn get_all_nymd_mixnodes_detailed( + pub async fn get_all_nyxd_mixnodes_detailed( &self, ) -> Result, ValidatorClientError> where @@ -341,7 +337,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_mixnodes_detailed_paged(self.mixnode_page_limit, start_after.take()) .await?; mixnodes.append(&mut paged_response.nodes); @@ -356,7 +352,7 @@ impl Client { Ok(mixnodes) } - pub async fn get_all_nymd_unbonded_mixnodes( + pub async fn get_all_nyxd_unbonded_mixnodes( &self, ) -> Result, ValidatorClientError> where @@ -366,7 +362,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_unbonded_paged(self.mixnode_page_limit, start_after.take()) .await?; mixnodes.append(&mut paged_response.nodes); @@ -381,7 +377,7 @@ impl Client { Ok(mixnodes) } - pub async fn get_all_nymd_unbonded_mixnodes_by_owner( + pub async fn get_all_nyxd_unbonded_mixnodes_by_owner( &self, owner: &cosmrs::AccountId, ) -> Result, ValidatorClientError> @@ -392,7 +388,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_unbonded_by_owner_paged(owner, self.mixnode_page_limit, start_after.take()) .await?; mixnodes.append(&mut paged_response.nodes); @@ -407,7 +403,7 @@ impl Client { Ok(mixnodes) } - pub async fn get_all_nymd_unbonded_mixnodes_by_identity( + pub async fn get_all_nyxd_unbonded_mixnodes_by_identity( &self, identity_key: String, ) -> Result, ValidatorClientError> @@ -418,7 +414,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_unbonded_by_identity_paged( identity_key.clone(), self.mixnode_page_limit, @@ -437,7 +433,7 @@ impl Client { Ok(mixnodes) } - pub async fn get_all_nymd_gateways(&self) -> Result, ValidatorClientError> + pub async fn get_all_nyxd_gateways(&self) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync + Send, { @@ -445,7 +441,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_gateways_paged(start_after.take(), self.gateway_page_limit) .await?; gateways.append(&mut paged_response.nodes); @@ -460,7 +456,7 @@ impl Client { Ok(gateways) } - pub async fn get_all_nymd_single_mixnode_delegations( + pub async fn get_all_nyxd_single_mixnode_delegations( &self, mix_id: MixId, ) -> Result, ValidatorClientError> @@ -471,7 +467,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_mixnode_delegations_paged( mix_id, start_after.take(), @@ -501,7 +497,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_delegator_delegations_paged( delegation_owner.to_string(), start_after.take(), @@ -528,7 +524,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_all_network_delegations_paged( start_after.take(), self.mixnode_delegations_page_limit, @@ -546,7 +542,7 @@ impl Client { Ok(delegations) } - pub async fn get_all_nymd_pending_epoch_events( + pub async fn get_all_nyxd_pending_epoch_events( &self, ) -> Result, ValidatorClientError> where @@ -557,7 +553,7 @@ impl Client { loop { let mut paged_response = self - .nymd + .nyxd .get_pending_epoch_events_paged(start_after.take(), self.rewarded_set_page_limit) .await?; events.append(&mut paged_response.events); @@ -572,7 +568,7 @@ impl Client { Ok(events) } - pub async fn get_all_nymd_pending_interval_events( + pub async fn get_all_nyxd_pending_interval_events( &self, ) -> Result, ValidatorClientError> where @@ -583,7 +579,7 @@ impl Client { loop { let mut paged_response = self - .nymd + .nyxd .get_pending_interval_events_paged(start_after.take(), self.rewarded_set_page_limit) .await?; events.append(&mut paged_response.events); @@ -598,7 +594,7 @@ impl Client { Ok(events) } - pub async fn get_all_nymd_current_dealers( + pub async fn get_all_nyxd_current_dealers( &self, ) -> Result, ValidatorClientError> where @@ -608,7 +604,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_current_dealers_paged(start_after.take(), self.dealers_page_limit) .await?; dealers.append(&mut paged_response.dealers); @@ -623,7 +619,7 @@ impl Client { Ok(dealers) } - pub async fn get_all_nymd_past_dealers( + pub async fn get_all_nyxd_past_dealers( &self, ) -> Result, ValidatorClientError> where @@ -633,7 +629,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_past_dealers_paged(start_after.take(), self.dealers_page_limit) .await?; dealers.append(&mut paged_response.dealers); @@ -648,7 +644,7 @@ impl Client { Ok(dealers) } - pub async fn get_all_nymd_epoch_dealings( + pub async fn get_all_nyxd_epoch_dealings( &self, idx: usize, ) -> Result, ValidatorClientError> @@ -659,7 +655,7 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd + .nyxd .get_dealings_paged(idx, start_after.take(), self.dealers_page_limit) .await?; dealings.append(&mut paged_response.dealings); @@ -674,8 +670,9 @@ impl Client { Ok(dealings) } - pub async fn get_all_nymd_verification_key_shares( + pub async fn get_all_nyxd_verification_key_shares( &self, + epoch_id: EpochId, ) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync + Send, @@ -684,8 +681,12 @@ impl Client { let mut start_after = None; loop { let mut paged_response = self - .nymd - .get_vk_shares_paged(start_after.take(), self.verification_key_page_limit) + .nyxd + .get_vk_shares_paged( + epoch_id, + start_after.take(), + self.verification_key_page_limit, + ) .await?; shares.append(&mut paged_response.shares); @@ -699,7 +700,7 @@ impl Client { Ok(shares) } - pub async fn get_all_nymd_proposals( + pub async fn get_all_nyxd_proposals( &self, ) -> Result, ValidatorClientError> where @@ -710,7 +711,7 @@ impl Client { loop { let mut paged_response = self - .nymd + .nyxd .list_proposals(start_after.take(), self.proposals_page_limit) .await?; @@ -729,77 +730,87 @@ impl Client { } // validator-api wrappers -#[cfg(feature = "nymd-client")] -impl Client { - pub fn change_validator_api(&mut self, new_endpoint: Url) { - self.validator_api.change_url(new_endpoint) +#[cfg(feature = "nyxd-client")] +impl Client +where + C: Clone, +{ + pub fn change_nym_api(&mut self, new_endpoint: Url) { + self.nym_api.change_url(new_endpoint) } pub async fn get_cached_mixnodes(&self) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_mixnodes().await?) + Ok(self.nym_api.get_mixnodes().await?) } pub async fn get_cached_mixnodes_detailed( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_mixnodes_detailed().await?) + Ok(self.nym_api.get_mixnodes_detailed().await?) + } + + pub async fn get_cached_mixnodes_detailed_unfiltered( + &self, + ) -> Result, ValidatorClientError> { + Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?) } pub async fn get_cached_rewarded_mixnodes( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_rewarded_mixnodes().await?) + Ok(self.nym_api.get_rewarded_mixnodes().await?) } pub async fn get_cached_rewarded_mixnodes_detailed( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_rewarded_mixnodes_detailed().await?) + Ok(self.nym_api.get_rewarded_mixnodes_detailed().await?) } pub async fn get_cached_active_mixnodes( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_active_mixnodes().await?) + Ok(self.nym_api.get_active_mixnodes().await?) } pub async fn get_cached_active_mixnodes_detailed( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_active_mixnodes_detailed().await?) + Ok(self.nym_api.get_active_mixnodes_detailed().await?) } pub async fn get_cached_gateways(&self) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_gateways().await?) + Ok(self.nym_api.get_gateways().await?) } pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, ) -> Result { - Ok(self.validator_api.blind_sign(request_body).await?) + Ok(self.nym_api.blind_sign(request_body).await?) } } #[derive(Clone)] pub struct CoconutApiClient { - pub api_client: ApiClient, + pub api_client: NymApiClient, pub verification_key: VerificationKey, pub node_id: NodeIndex, - #[cfg(feature = "nymd-client")] + #[cfg(feature = "nyxd-client")] pub cosmos_address: cosmrs::AccountId, } -#[cfg(feature = "nymd-client")] +#[cfg(feature = "nyxd-client")] impl CoconutApiClient { - pub async fn all_coconut_api_clients( - nymd_client: &Client, + pub async fn all_coconut_api_clients( + nyxd_client: &Client, + epoch_id: EpochId, ) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync + Send, { - Ok(nymd_client - .get_all_nymd_verification_key_shares() + Ok(nyxd_client + .get_all_nyxd_verification_key_shares(epoch_id) .await? .into_iter() .filter_map(Self::try_from) @@ -812,7 +823,7 @@ impl CoconutApiClient { if let Ok(verification_key) = VerificationKey::try_from_bs58(&share.share) { if let Ok(cosmos_address) = cosmrs::AccountId::from_str(share.owner.as_str()) { return Some(CoconutApiClient { - api_client: ApiClient::new(url_address), + api_client: NymApiClient::new(url_address), verification_key, node_id: share.node_index, cosmos_address, @@ -826,43 +837,41 @@ impl CoconutApiClient { } #[derive(Clone)] -pub struct ApiClient { - pub validator_api: validator_api::Client, +pub struct NymApiClient { + pub nym_api_client: nym_api::Client, // TODO: perhaps if we really need it at some (currently I don't see any reasons for it) // we could re-implement the communication with the REST API on port 1317 } -impl ApiClient { +impl NymApiClient { pub fn new(api_url: Url) -> Self { - let validator_api_client = validator_api::Client::new(api_url); + let nym_api_client = nym_api::Client::new(api_url); - ApiClient { - validator_api: validator_api_client, - } + NymApiClient { nym_api_client } } - pub fn change_validator_api(&mut self, new_endpoint: Url) { - self.validator_api.change_url(new_endpoint); + pub fn change_nym_api(&mut self, new_endpoint: Url) { + self.nym_api_client.change_url(new_endpoint); } pub async fn get_cached_active_mixnodes( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_active_mixnodes().await?) + Ok(self.nym_api_client.get_active_mixnodes().await?) } pub async fn get_cached_rewarded_mixnodes( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_rewarded_mixnodes().await?) + Ok(self.nym_api_client.get_rewarded_mixnodes().await?) } pub async fn get_cached_mixnodes(&self) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_mixnodes().await?) + Ok(self.nym_api_client.get_mixnodes().await?) } pub async fn get_cached_gateways(&self) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_gateways().await?) + Ok(self.nym_api_client.get_gateways().await?) } pub async fn get_gateway_core_status_count( @@ -871,7 +880,7 @@ impl ApiClient { since: Option, ) -> Result { Ok(self - .validator_api + .nym_api_client .get_gateway_core_status_count(identity, since) .await?) } @@ -882,7 +891,7 @@ impl ApiClient { since: Option, ) -> Result { Ok(self - .validator_api + .nym_api_client .get_mixnode_core_status_count(mix_id, since) .await?) } @@ -891,7 +900,7 @@ impl ApiClient { &self, mix_id: MixId, ) -> Result { - Ok(self.validator_api.get_mixnode_status(mix_id).await?) + Ok(self.nym_api_client.get_mixnode_status(mix_id).await?) } pub async fn get_mixnode_reward_estimation( @@ -899,7 +908,7 @@ impl ApiClient { mix_id: MixId, ) -> Result { Ok(self - .validator_api + .nym_api_client .get_mixnode_reward_estimation(mix_id) .await?) } @@ -909,7 +918,7 @@ impl ApiClient { mix_id: MixId, ) -> Result { Ok(self - .validator_api + .nym_api_client .get_mixnode_stake_saturation(mix_id) .await?) } @@ -918,17 +927,7 @@ impl ApiClient { &self, request_body: &BlindSignRequestBody, ) -> Result { - Ok(self.validator_api.blind_sign(request_body).await?) - } - - pub async fn partial_bandwidth_credential( - &self, - request_body: &str, - ) -> Result { - Ok(self - .validator_api - .partial_bandwidth_credential(request_body) - .await?) + Ok(self.nym_api_client.blind_sign(request_body).await?) } pub async fn verify_bandwidth_credential( @@ -936,7 +935,7 @@ impl ApiClient { request_body: &VerifyCredentialBody, ) -> Result { Ok(self - .validator_api + .nym_api_client .verify_bandwidth_credential(request_body) .await?) } diff --git a/common/client-libs/validator-client/src/connection_tester.rs b/common/client-libs/validator-client/src/connection_tester.rs index 30e57ff95e..fe7ded3e5b 100644 --- a/common/client-libs/validator-client/src/connection_tester.rs +++ b/common/client-libs/validator-client/src/connection_tester.rs @@ -1,12 +1,12 @@ -use crate::nymd::error::NymdError; -use crate::nymd::{Config as ClientConfig, NymdClient, QueryNymdClient}; -use crate::ApiClient; +use crate::nyxd::error::NyxdError; +use crate::nyxd::{Config as ClientConfig, NyxdClient, QueryNyxdClient}; +use crate::NymApiClient; -use crate::nymd::traits::MixnetQueryClient; +use crate::nyxd::traits::MixnetQueryClient; use colored::Colorize; use core::fmt; use itertools::Itertools; -use network_defaults::NymNetworkDetails; +use nym_network_defaults::NymNetworkDetails; use std::collections::HashMap; use std::hash::BuildHasher; use std::time::Duration; @@ -16,9 +16,9 @@ use url::Url; const MAX_URLS_TESTED: usize = 200; const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2; -// Run connection tests for all specified nymd and api urls. These are all run concurrently. +/// Run connection tests for all specified nyxd and api urls. These are all run concurrently. pub async fn run_validator_connection_test( - nymd_urls: impl Iterator, + nyxd_urls: impl Iterator, api_urls: impl Iterator, mixnet_contract_address: HashMap, ) -> ( @@ -27,7 +27,7 @@ pub async fn run_validator_connection_test( ) { // Setup all the clients for the connection tests let connection_test_clients = - setup_connection_tests(nymd_urls, api_urls, mixnet_contract_address); + setup_connection_tests(nyxd_urls, api_urls, mixnet_contract_address); // Run all tests concurrently let connection_results = futures::future::join_all( @@ -40,28 +40,28 @@ pub async fn run_validator_connection_test( // Seperate and collect results into HashMaps ( - extract_and_collect_results_into_map(&connection_results, &UrlType::Nymd), - extract_and_collect_results_into_map(&connection_results, &UrlType::Api), + extract_and_collect_results_into_map(&connection_results, &UrlType::Nyxd), + extract_and_collect_results_into_map(&connection_results, &UrlType::NymApi), ) } fn setup_connection_tests( - nymd_urls: impl Iterator, + nyxd_urls: impl Iterator, api_urls: impl Iterator, mixnet_contract_address: HashMap, ) -> impl Iterator { - let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| { + let nyxd_connection_test_clients = nyxd_urls.filter_map(move |(network, url)| { let address = mixnet_contract_address .get(&network) .expect("No configured contract address") .clone(); let config = ClientConfig::try_from_nym_network_details(&network) - .expect("failed to create valid nymd client config"); + .expect("failed to create valid nyxd client config"); - if let Ok(mut client) = NymdClient::::connect(config, url.as_str()) { + if let Ok(mut client) = NyxdClient::::connect(config, url.as_str()) { // possibly redundant, but lets just leave it here client.set_mixnet_contract_address(address); - Some(ClientForConnectionTest::Nymd( + Some(ClientForConnectionTest::Nyxd( network, url, Box::new(client), @@ -72,10 +72,10 @@ fn setup_connection_tests( }); let api_connection_test_clients = api_urls.map(|(network, url)| { - ClientForConnectionTest::Api(network, url.clone(), ApiClient::new(url)) + ClientForConnectionTest::Api(network, url.clone(), NymApiClient::new(url)) }); - nymd_connection_test_clients.chain(api_connection_test_clients) + nyxd_connection_test_clients.chain(api_connection_test_clients) } fn extract_and_collect_results_into_map( @@ -92,10 +92,10 @@ fn extract_and_collect_results_into_map( .into_group_map() } -async fn test_nymd_connection( +async fn test_nyxd_connection( network: NymNetworkDetails, url: &Url, - client: &NymdClient, + client: &NyxdClient, ) -> ConnectionResult { let result = match timeout( Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC), @@ -103,48 +103,48 @@ async fn test_nymd_connection( ) .await { - Ok(Err(NymdError::TendermintError(e))) => { + Ok(Err(NyxdError::TendermintError(e))) => { // If we get a tendermint-rpc error, we classify the node as not contactable - log::debug!("Checking: nymd_url: {url}: {}: {}", "failed".red(), e); + log::debug!("Checking: nyxd url: {url}: {}: {}", "failed".red(), e); false } - Ok(Err(NymdError::AbciError { code, log, .. })) => { + Ok(Err(NyxdError::AbciError { code, log, .. })) => { // We accept the mixnet contract not found as ok from a connection standpoint. This happens // for example on a pre-launch network. log::debug!( - "Checking: nymd_url: {url}: {}, but with abci error: {code}: {log}", + "Checking: nyxd url: {url}: {}, but with abci error: {code}: {log}", "success".green() ); code == 18 } - Ok(Err(error @ NymdError::NoContractAddressAvailable)) => { - log::debug!("Checking: nymd_url: {url}: {}: {error}", "failed".red()); + Ok(Err(error @ NyxdError::NoContractAddressAvailable)) => { + log::debug!("Checking: nyxd url: {url}: {}: {error}", "failed".red()); false } Ok(Err(e)) => { // For any other error, we're optimistic and just try anyway. log::debug!( - "Checking: nymd_url: {url}: {}, but with error: {e}", + "Checking: nyxd_url: {url}: {}, but with error: {e}", "success".green() ); true } Ok(Ok(_)) => { - log::debug!("Checking: nymd_url: {url}: {}", "success".green()); + log::debug!("Checking: nyxd_url: {url}: {}", "success".green()); true } Err(e) => { - log::debug!("Checking: nymd_url: {url}: {}: {e}", "failed".red()); + log::debug!("Checking: nyxd_url: {url}: {}: {e}", "failed".red()); false } }; - ConnectionResult::Nymd(network, url.clone(), result) + ConnectionResult::Nyxd(network, url.clone(), result) } -async fn test_api_connection( +async fn test_nym_api_connection( network: NymNetworkDetails, url: &Url, - client: &ApiClient, + client: &NymApiClient, ) -> ConnectionResult { let result = match timeout( Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC), @@ -169,18 +169,18 @@ async fn test_api_connection( } enum ClientForConnectionTest { - Nymd(NymNetworkDetails, Url, Box>), - Api(NymNetworkDetails, Url, ApiClient), + Nyxd(NymNetworkDetails, Url, Box>), + Api(NymNetworkDetails, Url, NymApiClient), } impl ClientForConnectionTest { async fn run_connection_check(self) -> ConnectionResult { match self { - ClientForConnectionTest::Nymd(network, ref url, ref client) => { - test_nymd_connection(network, url, client).await + ClientForConnectionTest::Nyxd(network, ref url, ref client) => { + test_nyxd_connection(network, url, client).await } ClientForConnectionTest::Api(network, ref url, ref client) => { - test_api_connection(network, url, client).await + test_nym_api_connection(network, url, client).await } } } @@ -188,37 +188,37 @@ impl ClientForConnectionTest { #[derive(Debug, PartialEq, Eq)] enum UrlType { - Nymd, - Api, + Nyxd, + NymApi, } impl fmt::Display for UrlType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - UrlType::Nymd => write!(f, "nymd"), - UrlType::Api => write!(f, "api"), + UrlType::Nyxd => write!(f, "nyxd"), + UrlType::NymApi => write!(f, "api"), } } } #[derive(Debug)] enum ConnectionResult { - Nymd(NymNetworkDetails, Url, bool), + Nyxd(NymNetworkDetails, Url, bool), Api(NymNetworkDetails, Url, bool), } impl ConnectionResult { fn result(&self) -> (&NymNetworkDetails, &Url, &bool) { match self { - ConnectionResult::Nymd(network, url, result) + ConnectionResult::Nyxd(network, url, result) | ConnectionResult::Api(network, url, result) => (network, url, result), } } fn url_type(&self) -> UrlType { match self { - ConnectionResult::Nymd(..) => UrlType::Nymd, - ConnectionResult::Api(..) => UrlType::Api, + ConnectionResult::Nyxd(..) => UrlType::Nyxd, + ConnectionResult::Api(..) => UrlType::NymApi, } } } diff --git a/common/client-libs/validator-client/src/error.rs b/common/client-libs/validator-client/src/error.rs index e832b12618..08da52c626 100644 --- a/common/client-libs/validator-client/src/error.rs +++ b/common/client-libs/validator-client/src/error.rs @@ -1,23 +1,23 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::validator_api; +use crate::nym_api; use thiserror::Error; #[derive(Error, Debug)] pub enum ValidatorClientError { - #[error("There was an issue with the validator api request - {source}")] - ValidatorAPIError { + #[error("nym api request failed - {source}")] + NymAPIError { #[from] - source: validator_api::error::ValidatorAPIError, + source: nym_api::error::NymAPIError, }, #[error("One of the provided URLs was malformed - {0}")] MalformedUrlProvided(#[from] url::ParseError), - #[cfg(feature = "nymd-client")] - #[error("There was an issue with the Nymd client - {0}")] - NymdError(#[from] crate::nymd::error::NymdError), + #[cfg(feature = "nyxd-client")] + #[error("nyxd request failed - {0}")] + NyxdError(#[from] crate::nyxd::error::NyxdError), #[error("No validator API url has been provided")] NoAPIUrlAvailable, diff --git a/common/client-libs/validator-client/src/lib.rs b/common/client-libs/validator-client/src/lib.rs index 2a5e010338..4bdd9275fd 100644 --- a/common/client-libs/validator-client/src/lib.rs +++ b/common/client-libs/validator-client/src/lib.rs @@ -2,17 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 pub mod client; -#[cfg(feature = "nymd-client")] +#[cfg(feature = "nyxd-client")] pub mod connection_tester; mod error; -#[cfg(feature = "nymd-client")] -pub mod nymd; -pub mod validator_api; +pub mod nym_api; +#[cfg(feature = "nyxd-client")] +pub mod nyxd; -#[cfg(feature = "nymd-client")] -pub use crate::client::{ApiClient, CoconutApiClient}; pub use crate::error::ValidatorClientError; -pub use validator_api_requests::*; +pub use nym_api_requests::*; -#[cfg(feature = "nymd-client")] -pub use client::{Client, Config}; +#[cfg(feature = "nyxd-client")] +pub use client::{Client, CoconutApiClient, Config, NymApiClient}; diff --git a/common/client-libs/validator-client/src/validator_api/error.rs b/common/client-libs/validator-client/src/nym_api/error.rs similarity index 60% rename from common/client-libs/validator-client/src/validator_api/error.rs rename to common/client-libs/validator-client/src/nym_api/error.rs index 9d7cd1d50d..1c3f7b11a4 100644 --- a/common/client-libs/validator-client/src/validator_api/error.rs +++ b/common/client-libs/validator-client/src/nym_api/error.rs @@ -1,8 +1,8 @@ +use nym_api_requests::models::RequestError; use thiserror::Error; -use validator_api_requests::models::RequestError; #[derive(Error, Debug)] -pub enum ValidatorAPIError { +pub enum NymAPIError { #[error("There was an issue with the REST request - {source}")] ReqwestClientError { #[from] @@ -12,6 +12,6 @@ pub enum ValidatorAPIError { #[error("Request failed with error message - {0}")] GenericRequestFailure(String), - #[error("The validator API has failed to resolve our request. It returned status code {status} and additional error message: {}", error.message())] + #[error("The nym API has failed to resolve our request. It returned status code {status} and additional error message: {}", error.message())] ApiRequestFailure { status: u16, error: RequestError }, } diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs similarity index 77% rename from common/client-libs/validator-client/src/validator_api/mod.rs rename to common/client-libs/validator-client/src/nym_api/mod.rs index 3cb7ae2ce9..10bea35417 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -1,22 +1,23 @@ // Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::validator_api::error::ValidatorAPIError; -use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG}; -use mixnet_contract_common::mixnode::MixNodeDetails; -use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId}; +use crate::nym_api::error::NymAPIError; +use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG}; +use nym_api_requests::coconut::{ + BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, +}; +use nym_api_requests::models::{ + ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse, + GatewayUptimeHistoryResponse, InclusionProbabilityResponse, MixNodeBondAnnotated, + MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse, + MixnodeUptimeHistoryResponse, RequestError, RewardEstimationResponse, StakeSaturationResponse, + UptimeResponse, +}; +use nym_mixnet_contract_common::mixnode::MixNodeDetails; +use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId}; use reqwest::Response; use serde::{Deserialize, Serialize}; use url::Url; -use validator_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, -}; -use validator_api_requests::models::{ - GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse, - InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse, - MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError, - RewardEstimationResponse, StakeSaturationResponse, UptimeResponse, -}; pub mod error; pub mod routes; @@ -53,7 +54,7 @@ impl Client { &self, path: PathSegments<'_>, params: Params<'_, K, V>, - ) -> Result + ) -> Result where K: AsRef, V: AsRef, @@ -62,11 +63,11 @@ impl Client { Ok(self.reqwest_client.get(url).send().await?) } - async fn query_validator_api( + async fn query_nym_api( &self, path: PathSegments<'_>, params: Params<'_, K, V>, - ) -> Result + ) -> Result where for<'a> T: Deserialize<'a>, K: AsRef, @@ -76,16 +77,16 @@ impl Client { if res.status().is_success() { Ok(res.json().await?) } else { - Err(ValidatorAPIError::GenericRequestFailure(res.text().await?)) + Err(NymAPIError::GenericRequestFailure(res.text().await?)) } } // This works for endpoints returning Result, ErrorResponse> - async fn query_validator_api_fallible( + async fn query_nym_api_fallible( &self, path: PathSegments<'_>, params: Params<'_, K, V>, - ) -> Result + ) -> Result where for<'a> T: Deserialize<'a>, K: AsRef, @@ -97,19 +98,19 @@ impl Client { Ok(res.json().await?) } else { let request_error: RequestError = res.json().await?; - Err(ValidatorAPIError::ApiRequestFailure { + Err(NymAPIError::ApiRequestFailure { status: status.as_u16(), error: request_error, }) } } - async fn post_validator_api( + async fn post_nym_api( &self, path: PathSegments<'_>, params: Params<'_, K, V>, json_body: &B, - ) -> Result + ) -> Result where B: Serialize + ?Sized, for<'a> T: Deserialize<'a>, @@ -121,21 +122,17 @@ impl Client { if response.status().is_success() { Ok(response.json().await?) } else { - Err(ValidatorAPIError::GenericRequestFailure( - response.text().await?, - )) + Err(NymAPIError::GenericRequestFailure(response.text().await?)) } } - pub async fn get_mixnodes(&self) -> Result, ValidatorAPIError> { - self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS) + pub async fn get_mixnodes(&self) -> Result, NymAPIError> { + self.query_nym_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS) .await } - pub async fn get_mixnodes_detailed( - &self, - ) -> Result, ValidatorAPIError> { - self.query_validator_api( + pub async fn get_mixnodes_detailed(&self) -> Result, NymAPIError> { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -147,13 +144,28 @@ impl Client { .await } - pub async fn get_gateways(&self) -> Result, ValidatorAPIError> { - self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS) + pub async fn get_mixnodes_detailed_unfiltered( + &self, + ) -> Result, NymAPIError> { + self.query_nym_api( + &[ + routes::API_VERSION, + routes::STATUS, + routes::MIXNODES, + routes::DETAILED_UNFILTERED, + ], + NO_PARAMS, + ) + .await + } + + pub async fn get_gateways(&self) -> Result, NymAPIError> { + self.query_nym_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS) .await } - pub async fn get_active_mixnodes(&self) -> Result, ValidatorAPIError> { - self.query_validator_api( + pub async fn get_active_mixnodes(&self) -> Result, NymAPIError> { + self.query_nym_api( &[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE], NO_PARAMS, ) @@ -162,8 +174,8 @@ impl Client { pub async fn get_active_mixnodes_detailed( &self, - ) -> Result, ValidatorAPIError> { - self.query_validator_api( + ) -> Result, NymAPIError> { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -176,8 +188,8 @@ impl Client { .await } - pub async fn get_rewarded_mixnodes(&self) -> Result, ValidatorAPIError> { - self.query_validator_api( + pub async fn get_rewarded_mixnodes(&self) -> Result, NymAPIError> { + self.query_nym_api( &[routes::API_VERSION, routes::MIXNODES, routes::REWARDED], NO_PARAMS, ) @@ -187,8 +199,8 @@ impl Client { pub async fn get_mixnode_report( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api( + ) -> Result { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -204,8 +216,8 @@ impl Client { pub async fn get_gateway_report( &self, identity: IdentityKeyRef<'_>, - ) -> Result { - self.query_validator_api( + ) -> Result { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -221,8 +233,8 @@ impl Client { pub async fn get_mixnode_history( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api( + ) -> Result { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -238,8 +250,8 @@ impl Client { pub async fn get_gateway_history( &self, identity: IdentityKeyRef<'_>, - ) -> Result { - self.query_validator_api( + ) -> Result { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -254,8 +266,8 @@ impl Client { pub async fn get_rewarded_mixnodes_detailed( &self, - ) -> Result, ValidatorAPIError> { - self.query_validator_api( + ) -> Result, NymAPIError> { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -272,9 +284,9 @@ impl Client { &self, identity: IdentityKeyRef<'_>, since: Option, - ) -> Result { + ) -> Result { if let Some(since) = since { - self.query_validator_api( + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -286,7 +298,7 @@ impl Client { ) .await } else { - self.query_validator_api( + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -303,9 +315,9 @@ impl Client { &self, mix_id: MixId, since: Option, - ) -> Result { + ) -> Result { if let Some(since) = since { - self.query_validator_api( + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -317,7 +329,7 @@ impl Client { ) .await } else { - self.query_validator_api( + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -334,8 +346,8 @@ impl Client { pub async fn get_mixnode_status( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api( + ) -> Result { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -351,8 +363,8 @@ impl Client { pub async fn get_mixnode_reward_estimation( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api_fallible( + ) -> Result { + self.query_nym_api_fallible( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -365,11 +377,30 @@ impl Client { .await } + pub async fn compute_mixnode_reward_estimation( + &self, + mix_id: MixId, + request_body: &ComputeRewardEstParam, + ) -> Result { + self.post_nym_api( + &[ + routes::API_VERSION, + routes::STATUS_ROUTES, + routes::MIXNODE, + &mix_id.to_string(), + routes::COMPUTE_REWARD_ESTIMATION, + ], + NO_PARAMS, + request_body, + ) + .await + } + pub async fn get_mixnode_stake_saturation( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api_fallible( + ) -> Result { + self.query_nym_api_fallible( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -385,8 +416,8 @@ impl Client { pub async fn get_mixnode_inclusion_probability( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api_fallible( + ) -> Result { + self.query_nym_api_fallible( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -402,8 +433,8 @@ impl Client { pub async fn get_mixnode_avg_uptime( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api_fallible( + ) -> Result { + self.query_nym_api_fallible( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -419,8 +450,8 @@ impl Client { pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, - ) -> Result { - self.post_validator_api( + ) -> Result { + self.post_nym_api( &[ routes::API_VERSION, routes::COCONUT_ROUTES, @@ -433,28 +464,11 @@ impl Client { .await } - pub async fn partial_bandwidth_credential( - &self, - request_body: &str, - ) -> Result { - self.post_validator_api( - &[ - routes::API_VERSION, - routes::COCONUT_ROUTES, - routes::BANDWIDTH, - routes::COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL, - ], - NO_PARAMS, - request_body, - ) - .await - } - pub async fn verify_bandwidth_credential( &self, request_body: &VerifyCredentialBody, - ) -> Result { - self.post_validator_api( + ) -> Result { + self.post_nym_api( &[ routes::API_VERSION, routes::COCONUT_ROUTES, diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/nym_api/routes.rs similarity index 82% rename from common/client-libs/validator-client/src/validator_api/routes.rs rename to common/client-libs/validator-client/src/nym_api/routes.rs index 66a822b4fd..8b6bdeca2f 100644 --- a/common/client-libs/validator-client/src/validator_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -1,20 +1,20 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use network_defaults::VALIDATOR_API_VERSION; +use nym_network_defaults::NYM_API_VERSION; -pub const API_VERSION: &str = VALIDATOR_API_VERSION; +pub const API_VERSION: &str = NYM_API_VERSION; pub const MIXNODES: &str = "mixnodes"; pub const GATEWAYS: &str = "gateways"; pub const DETAILED: &str = "detailed"; +pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered"; pub const ACTIVE: &str = "active"; pub const REWARDED: &str = "rewarded"; pub const COCONUT_ROUTES: &str = "coconut"; pub const BANDWIDTH: &str = "bandwidth"; pub const COCONUT_BLIND_SIGN: &str = "blind-sign"; -pub const COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL: &str = "partial-bandwidth-credential"; pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential"; pub const STATUS_ROUTES: &str = "status"; @@ -28,6 +28,7 @@ pub const STATUS: &str = "status"; pub const REPORT: &str = "report"; pub const HISTORY: &str = "history"; pub const REWARD_ESTIMATION: &str = "reward-estimation"; +pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation"; pub const AVG_UPTIME: &str = "avg_uptime"; pub const STAKE_SATURATION: &str = "stake-saturation"; pub const INCLUSION_CHANCE: &str = "inclusion-probability"; diff --git a/common/client-libs/validator-client/src/nymd/traits/vesting_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/vesting_query_client.rs deleted file mode 100644 index 598818197a..0000000000 --- a/common/client-libs/validator-client/src/nymd/traits/vesting_query_client.rs +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::nymd::coin::Coin; -pub use crate::nymd::cosmwasm_client::client::CosmWasmClient; -use crate::nymd::error::NymdError; -use crate::nymd::NymdClient; -use async_trait::async_trait; -use contracts_common::ContractBuildInformation; -use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp}; -use mixnet_contract_common::MixId; -use serde::Deserialize; -use vesting_contract::vesting::Account; -use vesting_contract_common::{ - messages::QueryMsg as VestingQueryMsg, AllDelegationsResponse, DelegationTimesResponse, - OriginalVestingResponse, Period, PledgeData, VestingDelegation, -}; - -#[async_trait] -pub trait VestingQueryClient { - async fn query_vesting_contract(&self, query: VestingQueryMsg) -> Result - where - for<'a> T: Deserialize<'a>; - - async fn get_vesting_contract_version(&self) -> Result { - self.query_vesting_contract(VestingQueryMsg::GetContractVersion {}) - .await - } - - async fn locked_coins( - &self, - address: &str, - block_time: Option, - ) -> Result; - - async fn spendable_coins( - &self, - vesting_account_address: &str, - block_time: Option, - ) -> Result; - - async fn vested_coins( - &self, - vesting_account_address: &str, - block_time: Option, - ) -> Result; - - async fn vesting_coins( - &self, - vesting_account_address: &str, - block_time: Option, - ) -> Result; - - async fn vesting_start_time( - &self, - vesting_account_address: &str, - ) -> Result; - - async fn vesting_end_time(&self, vesting_account_address: &str) - -> Result; - - async fn original_vesting( - &self, - vesting_account_address: &str, - ) -> Result; - - async fn delegated_free( - &self, - vesting_account_address: &str, - block_time: Option, - ) -> Result; - - async fn delegated_vesting( - &self, - vesting_account_address: &str, - block_time: Option, - ) -> Result; - - async fn get_account(&self, address: &str) -> Result; - async fn get_mixnode_pledge(&self, address: &str) -> Result, NymdError>; - async fn get_gateway_pledge(&self, address: &str) -> Result, NymdError>; - async fn get_current_vesting_period( - &self, - vesting_account_address: &str, - ) -> Result; - - async fn get_delegation_timestamps( - &self, - address: &str, - mix_id: MixId, - ) -> Result; - - async fn get_all_vesting_delegations_paged( - &self, - start_after: Option<(u32, MixId, u64)>, - limit: Option, - ) -> Result; - - async fn get_all_vesting_delegations(&self) -> Result, NymdError> { - let mut delegations = Vec::new(); - let mut start_after = None; - loop { - let mut paged_response = self - .get_all_vesting_delegations_paged(start_after.take(), None) - .await?; - delegations.append(&mut paged_response.delegations); - - if let Some(start_after_res) = paged_response.start_next_after { - start_after = Some(start_after_res) - } else { - break; - } - } - - Ok(delegations) - } -} - -#[async_trait] -impl VestingQueryClient for NymdClient { - async fn query_vesting_contract(&self, query: VestingQueryMsg) -> Result - where - for<'a> T: Deserialize<'a>, - { - self.client - .query_contract_smart(self.vesting_contract_address(), &query) - .await - } - - async fn locked_coins( - &self, - vesting_account_address: &str, - block_time: Option, - ) -> Result { - let request = VestingQueryMsg::LockedCoins { - vesting_account_address: vesting_account_address.to_string(), - block_time, - }; - self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) - .await - .map(Into::into) - } - - async fn spendable_coins( - &self, - vesting_account_address: &str, - block_time: Option, - ) -> Result { - let request = VestingQueryMsg::SpendableCoins { - vesting_account_address: vesting_account_address.to_string(), - block_time, - }; - self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) - .await - .map(Into::into) - } - async fn vested_coins( - &self, - vesting_account_address: &str, - block_time: Option, - ) -> Result { - let request = VestingQueryMsg::GetVestedCoins { - vesting_account_address: vesting_account_address.to_string(), - block_time, - }; - self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) - .await - .map(Into::into) - } - async fn vesting_coins( - &self, - vesting_account_address: &str, - block_time: Option, - ) -> Result { - let request = VestingQueryMsg::GetVestingCoins { - vesting_account_address: vesting_account_address.to_string(), - block_time, - }; - self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) - .await - .map(Into::into) - } - - async fn vesting_start_time( - &self, - vesting_account_address: &str, - ) -> Result { - let request = VestingQueryMsg::GetStartTime { - vesting_account_address: vesting_account_address.to_string(), - }; - self.client - .query_contract_smart(self.vesting_contract_address(), &request) - .await - } - - async fn vesting_end_time( - &self, - vesting_account_address: &str, - ) -> Result { - let request = VestingQueryMsg::GetEndTime { - vesting_account_address: vesting_account_address.to_string(), - }; - self.client - .query_contract_smart(self.vesting_contract_address(), &request) - .await - } - - async fn original_vesting( - &self, - vesting_account_address: &str, - ) -> Result { - let request = VestingQueryMsg::GetOriginalVesting { - vesting_account_address: vesting_account_address.to_string(), - }; - self.client - .query_contract_smart(self.vesting_contract_address(), &request) - .await - } - - async fn delegated_free( - &self, - vesting_account_address: &str, - block_time: Option, - ) -> Result { - let request = VestingQueryMsg::GetDelegatedFree { - vesting_account_address: vesting_account_address.to_string(), - block_time, - }; - self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) - .await - .map(Into::into) - } - - /// Returns the total amount of delegated tokens that have vested - async fn delegated_vesting( - &self, - vesting_account_address: &str, - block_time: Option, - ) -> Result { - let request = VestingQueryMsg::GetDelegatedVesting { - vesting_account_address: vesting_account_address.to_string(), - block_time, - }; - self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) - .await - .map(Into::into) - } - - async fn get_account(&self, address: &str) -> Result { - let request = VestingQueryMsg::GetAccount { - address: address.to_string(), - }; - self.client - .query_contract_smart(self.vesting_contract_address(), &request) - .await - } - async fn get_mixnode_pledge(&self, address: &str) -> Result, NymdError> { - let request = VestingQueryMsg::GetMixnode { - address: address.to_string(), - }; - self.client - .query_contract_smart(self.vesting_contract_address(), &request) - .await - } - async fn get_gateway_pledge(&self, address: &str) -> Result, NymdError> { - let request = VestingQueryMsg::GetGateway { - address: address.to_string(), - }; - self.client - .query_contract_smart(self.vesting_contract_address(), &request) - .await - } - - async fn get_current_vesting_period(&self, address: &str) -> Result { - let request = VestingQueryMsg::GetCurrentVestingPeriod { - address: address.to_string(), - }; - self.client - .query_contract_smart(self.vesting_contract_address(), &request) - .await - } - - async fn get_delegation_timestamps( - &self, - address: &str, - mix_id: MixId, - ) -> Result { - let request = VestingQueryMsg::GetDelegationTimes { - address: address.to_string(), - mix_id, - }; - self.client - .query_contract_smart(self.vesting_contract_address(), &request) - .await - } - - async fn get_all_vesting_delegations_paged( - &self, - start_after: Option<(u32, MixId, u64)>, - limit: Option, - ) -> Result { - let request = VestingQueryMsg::GetAllDelegations { start_after, limit }; - self.client - .query_contract_smart(self.vesting_contract_address(), &request) - .await - } -} diff --git a/common/client-libs/validator-client/src/nymd/coin.rs b/common/client-libs/validator-client/src/nyxd/coin.rs similarity index 53% rename from common/client-libs/validator-client/src/nymd/coin.rs rename to common/client-libs/validator-client/src/nyxd/coin.rs index 8f1afe2a30..3ac51997b8 100644 --- a/common/client-libs/validator-client/src/nymd/coin.rs +++ b/common/client-libs/validator-client/src/nyxd/coin.rs @@ -1,10 +1,13 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use serde::{Deserialize, Serialize}; -use std::fmt; +use crate::nyxd::{Gas, GasPrice}; pub use cosmrs::Coin as CosmosCoin; pub use cosmwasm_std::Coin as CosmWasmCoin; +use cosmwasm_std::{Fraction, Uint128}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::ops::Div; #[derive(Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq)] pub struct MismatchedDenoms; @@ -19,6 +22,40 @@ pub struct Coin { pub denom: String, } +impl Div for Coin { + type Output = Gas; + + fn div(self, rhs: GasPrice) -> Self::Output { + &self / rhs + } +} + +impl<'a> Div for &'a Coin { + type Output = Gas; + + fn div(self, rhs: GasPrice) -> Self::Output { + if self.denom != rhs.denom { + panic!( + "attempted to use two different denoms for gas calculation ({} and {})", + self.denom, rhs.denom + ); + } + + // tsk, tsk. somebody tried to divide by zero here! + let Some(gas_price_inv) = rhs.amount.inv() else { + panic!("attempted to divide by zero!") + }; + + let implicit_gas_limit = gas_price_inv * Uint128::new(self.amount); + if implicit_gas_limit.u128() >= u64::MAX as u128 { + u64::MAX + } else { + implicit_gas_limit.u128() as u64 + } + .into() + } +} + impl Coin { pub fn new>(amount: u128, denom: S) -> Self { Coin { @@ -128,3 +165,67 @@ impl CoinConverter for CosmWasmCoin { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn division_by_zero_gas_price() { + let gas_price: GasPrice = "0unym".parse().unwrap(); + let amount = Coin::new(123, "unym"); + let _res = amount / gas_price; + } + + #[test] + #[should_panic] + fn division_by_gas_price_of_different_denom() { + let gas_price: GasPrice = "0.025unyx".parse().unwrap(); + let amount = Coin::new(123, "unym"); + let _res = amount / gas_price; + } + + #[test] + fn gas_price_division() { + let amount = Coin::new(3938, "unym"); + let gas_price = "0.025unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(157520, res.value()); + + let amount = Coin::new(1234567890, "unym"); + let gas_price = "0.025unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(49382715600, res.value()); + + let amount = Coin::new(1, "unym"); + let gas_price = "0.025unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(40, res.value()); + + let amount = Coin::new(150_000_000, "unym"); + let gas_price = "0.001234unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(121555915721, res.value()); + + let amount = Coin::new(150_000_000, "unym"); + let gas_price = "1unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(150_000_000, res.value()); + + let amount = Coin::new(150_000_000, "unym"); + let gas_price = "1234.56unym".parse().unwrap(); + let res = amount / gas_price; + assert_eq!(121500, res.value()); + } + + #[test] + fn gas_price_division_identity() { + let amount = Coin::new(1234567890, "unym"); + let gas_price: GasPrice = "0.025unym".parse().unwrap(); + let res1 = (&amount) / gas_price.clone(); + let res2 = &gas_price * res1; + + assert_eq!(amount, Coin::from(res2)); + } +} diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/client.rs similarity index 89% rename from common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs rename to common/client-libs/validator-client/src/nyxd/cosmwasm_client/client.rs index 580a8c7c05..9be833c68b 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/client.rs @@ -1,14 +1,14 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd; -use crate::nymd::coin::Coin; -use crate::nymd::cosmwasm_client::helpers::{create_pagination, next_page_key}; -use crate::nymd::cosmwasm_client::types::{ +use crate::nyxd; +use crate::nyxd::coin::Coin; +use crate::nyxd::cosmwasm_client::helpers::{create_pagination, next_page_key}; +use crate::nyxd::cosmwasm_client::types::{ Account, Code, CodeDetails, Contract, ContractCodeHistoryEntry, ContractCodeId, SequenceResponse, SimulateResponse, }; -use crate::nymd::error::NymdError; +use crate::nyxd::error::NyxdError; use async_trait::async_trait; use cosmrs::proto::cosmos::auth::v1beta1::{QueryAccountRequest, QueryAccountResponse}; use cosmrs::proto::cosmos::bank::v1beta1::{ @@ -57,7 +57,7 @@ pub trait CosmWasmClient: rpc::Client { &self, path: Option, req: Req, - ) -> Result + ) -> Result where Req: Message, Res: Message + Default, @@ -66,21 +66,21 @@ pub trait CosmWasmClient: rpc::Client { req.encode(&mut buf)?; let res = self.abci_query(path, buf, None, false).await?; - let res_success = nymd::error::parse_abci_query_result(res)?; + let res_success = nyxd::error::parse_abci_query_result(res)?; Ok(Res::decode(res_success.value.as_ref())?) } - async fn get_chain_id(&self) -> Result { + async fn get_chain_id(&self) -> Result { Ok(self.status().await?.node_info.network) } - async fn get_height(&self) -> Result { + async fn get_height(&self) -> Result { Ok(self.status().await?.sync_info.latest_block_height) } // TODO: the return type should probably be changed to a non-proto, type-safe Account alternative - async fn get_account(&self, address: &AccountId) -> Result, NymdError> { + async fn get_account(&self, address: &AccountId) -> Result, NyxdError> { let path = Some("/cosmos.auth.v1beta1.Query/Account".parse().unwrap()); let req = QueryAccountRequest { @@ -94,11 +94,11 @@ pub trait CosmWasmClient: rpc::Client { res.account.map(TryFrom::try_from).transpose() } - async fn get_sequence(&self, address: &AccountId) -> Result { + async fn get_sequence(&self, address: &AccountId) -> Result { let account = self .get_account(address) .await? - .ok_or_else(|| NymdError::NonExistentAccountError(address.clone()))?; + .ok_or_else(|| NyxdError::NonExistentAccountError(address.clone()))?; let base_account = account.try_get_base_account()?; Ok(SequenceResponse { @@ -107,7 +107,7 @@ pub trait CosmWasmClient: rpc::Client { }) } - async fn get_block(&self, height: Option) -> Result { + async fn get_block(&self, height: Option) -> Result { match height { Some(height) => self.block(height).await.map_err(|err| err.into()), None => self.latest_block().await.map_err(|err| err.into()), @@ -118,7 +118,7 @@ pub trait CosmWasmClient: rpc::Client { &self, address: &AccountId, search_denom: String, - ) -> Result, NymdError> { + ) -> Result, NyxdError> { let path = Some("/cosmos.bank.v1beta1.Query/Balance".parse().unwrap()); let req = QueryBalanceRequest { @@ -133,10 +133,10 @@ pub trait CosmWasmClient: rpc::Client { res.balance .map(|proto| CosmosCoin::try_from(proto).map(Into::into)) .transpose() - .map_err(|_| NymdError::SerializationError("Coin".to_owned())) + .map_err(|_| NyxdError::SerializationError("Coin".to_owned())) } - async fn get_all_balances(&self, address: &AccountId) -> Result, NymdError> { + async fn get_all_balances(&self, address: &AccountId) -> Result, NyxdError> { let path = Some("/cosmos.bank.v1beta1.Query/AllBalances".parse().unwrap()); let mut raw_balances = Vec::new(); @@ -164,10 +164,10 @@ pub trait CosmWasmClient: rpc::Client { .into_iter() .map(|proto| CosmosCoin::try_from(proto).map(Into::into)) .collect::>() - .map_err(|_| NymdError::SerializationError("Coins".to_owned())) + .map_err(|_| NyxdError::SerializationError("Coins".to_owned())) } - async fn get_total_supply(&self) -> Result, NymdError> { + async fn get_total_supply(&self) -> Result, NyxdError> { let path = Some("/cosmos.bank.v1beta1.Query/TotalSupply".parse().unwrap()); let mut supply = Vec::new(); @@ -192,14 +192,14 @@ pub trait CosmWasmClient: rpc::Client { .into_iter() .map(|proto| CosmosCoin::try_from(proto).map(Into::into)) .collect::>() - .map_err(|_| NymdError::SerializationError("Coins".to_owned())) + .map_err(|_| NyxdError::SerializationError("Coins".to_owned())) } - async fn get_tx(&self, id: tx::Hash) -> Result { + async fn get_tx(&self, id: tx::Hash) -> Result { Ok(self.tx(id, false).await?) } - async fn search_tx(&self, query: Query) -> Result, NymdError> { + async fn search_tx(&self, query: Query) -> Result, NyxdError> { // according to https://docs.tendermint.com/master/rpc/#/Info/tx_search // the maximum entries per page is 100 and the default is 30 // so let's attempt to use the maximum @@ -234,7 +234,7 @@ pub trait CosmWasmClient: rpc::Client { async fn broadcast_tx_async( &self, tx: Transaction, - ) -> Result { + ) -> Result { Ok(rpc::Client::broadcast_tx_async(self, tx).await?) } @@ -242,7 +242,7 @@ pub trait CosmWasmClient: rpc::Client { async fn broadcast_tx_sync( &self, tx: Transaction, - ) -> Result { + ) -> Result { Ok(rpc::Client::broadcast_tx_sync(self, tx).await?) } @@ -250,16 +250,16 @@ pub trait CosmWasmClient: rpc::Client { async fn broadcast_tx_commit( &self, tx: Transaction, - ) -> Result { + ) -> Result { Ok(rpc::Client::broadcast_tx_commit(self, tx).await?) } - async fn broadcast_tx(&self, tx: Transaction) -> Result { + async fn broadcast_tx(&self, tx: Transaction) -> Result { let broadcasted = CosmWasmClient::broadcast_tx_sync(self, tx).await?; if broadcasted.code.is_err() { let code_val = broadcasted.code.value(); - return Err(NymdError::BroadcastTxErrorDeliverTx { + return Err(NyxdError::BroadcastTxErrorDeliverTx { hash: broadcasted.hash, height: None, code: code_val, @@ -276,7 +276,7 @@ pub trait CosmWasmClient: rpc::Client { broadcasted.hash ); if tokio::time::Instant::now().duration_since(start) >= self.broadcast_timeout() { - return Err(NymdError::BroadcastTimeout { + return Err(NyxdError::BroadcastTimeout { hash: tx_hash, timeout: self.broadcast_timeout(), }); @@ -290,7 +290,7 @@ pub trait CosmWasmClient: rpc::Client { } } - async fn get_codes(&self) -> Result, NymdError> { + async fn get_codes(&self) -> Result, NyxdError> { let path = Some("/cosmwasm.wasm.v1.Query/Codes".parse().unwrap()); let mut raw_codes = Vec::new(); @@ -317,7 +317,7 @@ pub trait CosmWasmClient: rpc::Client { .collect::>() } - async fn get_code_details(&self, code_id: ContractCodeId) -> Result { + async fn get_code_details(&self, code_id: ContractCodeId) -> Result { let path = Some("/cosmwasm.wasm.v1.Query/Code".parse().unwrap()); let req = QueryCodeRequest { code_id }; @@ -329,10 +329,10 @@ pub trait CosmWasmClient: rpc::Client { if let Some(code_info) = res.code_info { Ok(CodeDetails::new(code_info.try_into()?, res.data)) } else { - Err(NymdError::NoCodeInformation(code_id)) + Err(NyxdError::NoCodeInformation(code_id)) } } - async fn get_contracts(&self, code_id: ContractCodeId) -> Result, NymdError> { + async fn get_contracts(&self, code_id: ContractCodeId) -> Result, NyxdError> { let path = Some("/cosmwasm.wasm.v1.Query/ContractsByCode".parse().unwrap()); let mut raw_contracts = Vec::new(); @@ -360,10 +360,10 @@ pub trait CosmWasmClient: rpc::Client { .iter() .map(|raw| raw.parse()) .collect::>() - .map_err(|_| NymdError::DeserializationError("Contract addresses".to_owned())) + .map_err(|_| NyxdError::DeserializationError("Contract addresses".to_owned())) } - async fn get_contract(&self, address: &AccountId) -> Result { + async fn get_contract(&self, address: &AccountId) -> Result { let path = Some("/cosmwasm.wasm.v1.Query/ContractInfo".parse().unwrap()); let req = QueryContractInfoRequest { @@ -378,17 +378,17 @@ pub trait CosmWasmClient: rpc::Client { if let Some(contract_info) = res.contract_info { let address = response_address .parse() - .map_err(|_| NymdError::MalformedAccountAddress(response_address))?; + .map_err(|_| NyxdError::MalformedAccountAddress(response_address))?; Ok(Contract::new(address, contract_info.try_into()?)) } else { - Err(NymdError::NoContractInformation(address.clone())) + Err(NyxdError::NoContractInformation(address.clone())) } } async fn get_contract_code_history( &self, address: &AccountId, - ) -> Result, NymdError> { + ) -> Result, NyxdError> { let path = Some("/cosmwasm.wasm.v1.Query/ContractHistory".parse().unwrap()); let mut raw_entries = Vec::new(); @@ -422,7 +422,7 @@ pub trait CosmWasmClient: rpc::Client { &self, address: &AccountId, query_data: Vec, - ) -> Result, NymdError> { + ) -> Result, NyxdError> { let path = Some("/cosmwasm.wasm.v1.Query/RawContractState".parse().unwrap()); let req = QueryRawContractStateRequest { @@ -441,7 +441,7 @@ pub trait CosmWasmClient: rpc::Client { &self, address: &AccountId, query_msg: &M, - ) -> Result + ) -> Result where M: ?Sized + Serialize + Sync, for<'a> T: Deserialize<'a>, @@ -478,7 +478,7 @@ pub trait CosmWasmClient: rpc::Client { &self, tx: Option, tx_bytes: Vec, - ) -> Result { + ) -> Result { let path = Some("/cosmos.tx.v1beta1.Service/Simulate".parse().unwrap()); let req = SimulateRequest { diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/helpers.rs similarity index 78% rename from common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs rename to common/client-libs/validator-client/src/nyxd/cosmwasm_client/helpers.rs index ce1e93dcdb..0dd7f98ead 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/helpers.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::error::NymdError; +use crate::nyxd::error::NyxdError; use cosmrs::proto::cosmos::base::query::v1beta1::{PageRequest, PageResponse}; use cosmrs::proto::cosmos::base::v1beta1::Coin as ProtoCoin; use cosmrs::rpc::endpoint::broadcast; @@ -11,13 +11,13 @@ use flate2::Compression; use std::io::Write; pub(crate) trait CheckResponse: Sized { - fn check_response(self) -> Result; + fn check_response(self) -> Result; } impl CheckResponse for broadcast::tx_commit::Response { - fn check_response(self) -> Result { + fn check_response(self) -> Result { if self.check_tx.code.is_err() { - return Err(NymdError::BroadcastTxErrorCheckTx { + return Err(NyxdError::BroadcastTxErrorCheckTx { hash: self.hash, height: Some(self.height), code: self.check_tx.code.value(), @@ -26,7 +26,7 @@ impl CheckResponse for broadcast::tx_commit::Response { } if self.deliver_tx.code.is_err() { - return Err(NymdError::BroadcastTxErrorDeliverTx { + return Err(NyxdError::BroadcastTxErrorDeliverTx { hash: self.hash, height: Some(self.height), code: self.deliver_tx.code.value(), @@ -38,10 +38,10 @@ impl CheckResponse for broadcast::tx_commit::Response { } } -impl CheckResponse for crate::nymd::TxResponse { - fn check_response(self) -> Result { +impl CheckResponse for crate::nyxd::TxResponse { + fn check_response(self) -> Result { if self.tx_result.code.is_err() { - return Err(NymdError::BroadcastTxErrorDeliverTx { + return Err(NyxdError::BroadcastTxErrorDeliverTx { hash: self.hash, height: Some(self.height), code: self.tx_result.code.value(), @@ -53,13 +53,13 @@ impl CheckResponse for crate::nymd::TxResponse { } } -pub(crate) fn compress_wasm_code(code: &[u8]) -> Result, NymdError> { +pub(crate) fn compress_wasm_code(code: &[u8]) -> Result, NyxdError> { // using compression level 9, same as cosmjs, that optimises for size let mut encoder = GzEncoder::new(Vec::new(), Compression::best()); encoder .write_all(code) - .map_err(NymdError::WasmCompressionError)?; - encoder.finish().map_err(NymdError::WasmCompressionError) + .map_err(NyxdError::WasmCompressionError)?; + encoder.finish().map_err(NyxdError::WasmCompressionError) } pub(crate) fn create_pagination(key: Vec) -> PageRequest { @@ -84,11 +84,11 @@ pub(crate) fn next_page_key(pagination_info: Option) -> Option) -> Result, NymdError> { +pub(crate) fn parse_proto_coin_vec(value: Vec) -> Result, NyxdError> { value .into_iter() .map(|proto_coin| { - Coin::try_from(&proto_coin).map_err(|_| NymdError::MalformedCoin { + Coin::try_from(&proto_coin).map_err(|_| NyxdError::MalformedCoin { coin_representation: format!("{:?}", proto_coin), }) }) diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/logs.rs similarity index 91% rename from common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs rename to common/client-libs/validator-client/src/nyxd/cosmwasm_client/logs.rs index f08903b99c..3912500f47 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/logs.rs @@ -1,13 +1,13 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::error::NymdError; +use crate::nyxd::error::NyxdError; use cosmrs::tendermint::abci; use itertools::Itertools; use serde::{Deserialize, Serialize}; -pub use coconut_bandwidth_contract_common::event_attributes::*; -pub use coconut_dkg_common::event_attributes::*; +pub use nym_coconut_bandwidth_contract_common::event_attributes::*; +pub use nym_coconut_dkg_common::event_attributes::*; // it seems that currently validators just emit stringified events (which are also returned as part of deliverTx response) // as theirs logs @@ -39,17 +39,17 @@ pub fn find_attribute<'a>( } // those two functions were separated so that the internal logic could actually be tested -fn parse_raw_str_logs(raw: &str) -> Result, NymdError> { - let logs: Vec = serde_json::from_str(raw).map_err(|_| NymdError::MalformedLogString)?; +fn parse_raw_str_logs(raw: &str) -> Result, NyxdError> { + let logs: Vec = serde_json::from_str(raw).map_err(|_| NyxdError::MalformedLogString)?; if logs.len() != logs.iter().unique_by(|log| log.msg_index).count() { // this check is only here because I don't yet fully understand raw log string generation and // the fact the first entry does not seem to have `msg_index` defined on it. - return Err(NymdError::MalformedLogString); + return Err(NyxdError::MalformedLogString); } Ok(logs) } -pub fn parse_raw_logs(raw: abci::Log) -> Result, NymdError> { +pub fn parse_raw_logs(raw: abci::Log) -> Result, NyxdError> { parse_raw_str_logs(raw.as_ref()) } diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs similarity index 77% rename from common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs rename to common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs index 2af7b52704..ef8bcdc5d7 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs @@ -1,9 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::error::NymdError; -use crate::nymd::wallet::DirectSecp256k1HdWallet; -use crate::nymd::GasPrice; +use crate::nyxd::error::NyxdError; +use crate::nyxd::wallet::DirectSecp256k1HdWallet; +use crate::nyxd::GasPrice; use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl}; use std::convert::TryInto; @@ -13,7 +13,7 @@ pub mod logs; pub mod signing_client; pub mod types; -pub fn connect(endpoint: U) -> Result +pub fn connect(endpoint: U) -> Result where U: TryInto, { @@ -25,7 +25,7 @@ pub fn connect_with_signer( endpoint: U, signer: DirectSecp256k1HdWallet, gas_price: GasPrice, -) -> Result +) -> Result where U: TryInto, { diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/signing_client.rs similarity index 87% rename from common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs rename to common/client-libs/validator-client/src/nyxd/cosmwasm_client/signing_client.rs index b1646f663e..58ec47bbcb 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/signing_client.rs @@ -1,14 +1,14 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::cosmwasm_client::client::CosmWasmClient; -use crate::nymd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse}; -use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs}; -use crate::nymd::cosmwasm_client::types::*; -use crate::nymd::error::NymdError; -use crate::nymd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER}; -use crate::nymd::wallet::DirectSecp256k1HdWallet; -use crate::nymd::{Coin, GasAdjustable, GasPrice, TxResponse}; +use crate::nyxd::cosmwasm_client::client::CosmWasmClient; +use crate::nyxd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse}; +use crate::nyxd::cosmwasm_client::logs::{self, parse_raw_logs}; +use crate::nyxd::cosmwasm_client::types::*; +use crate::nyxd::error::NyxdError; +use crate::nyxd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER}; +use crate::nyxd::wallet::DirectSecp256k1HdWallet; +use crate::nyxd::{Coin, GasAdjustable, GasPrice, TxResponse}; use async_trait::async_trait; use cosmrs::bank::MsgSend; use cosmrs::distribution::MsgWithdrawDelegatorReward; @@ -74,7 +74,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { signer_address: &AccountId, messages: Vec, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let public_key = self.signer_public_key(signer_address); let sequence_response = self.get_sequence(signer_address).await?; @@ -102,7 +102,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { wasm_code: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let compressed = compress_wasm_code(&wasm_code)?; let compressed_size = compressed.len(); let compressed_checksum = Sha256::digest(&compressed).to_vec(); @@ -115,7 +115,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { instantiate_permission: Default::default(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgStoreCode".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgStoreCode".to_owned()))?; let tx_res = self .sign_and_broadcast(sender_address, vec![upload_msg], fee, memo) @@ -161,7 +161,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { fee: Fee, memo: impl Into + Send + 'static, mut options: Option, - ) -> Result + ) -> Result where M: ?Sized + Serialize + Sync, { @@ -176,7 +176,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { funds: options.map(|options| options.funds).unwrap_or_default(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgInstantiateContract".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgInstantiateContract".to_owned()))?; let tx_res = self .sign_and_broadcast(sender_address, vec![init_msg], fee, memo) @@ -211,14 +211,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient { new_admin: &AccountId, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let change_admin_msg = cosmwasm::MsgUpdateAdmin { sender: sender_address.clone(), new_admin: new_admin.clone(), contract: contract_address.clone(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgUpdateAdmin".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgUpdateAdmin".to_owned()))?; let tx_res = self .sign_and_broadcast(sender_address, vec![change_admin_msg], fee, memo) @@ -240,13 +240,13 @@ pub trait SigningCosmWasmClient: CosmWasmClient { contract_address: &AccountId, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let change_admin_msg = cosmwasm::MsgClearAdmin { sender: sender_address.clone(), contract: contract_address.clone(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgClearAdmin".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgClearAdmin".to_owned()))?; let tx_res = self .sign_and_broadcast(sender_address, vec![change_admin_msg], fee, memo) @@ -270,7 +270,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { fee: Fee, msg: &M, memo: impl Into + Send + 'static, - ) -> Result + ) -> Result where M: ?Sized + Serialize + Sync, { @@ -281,7 +281,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { msg: serde_json::to_vec(msg)?, } .to_any() - .map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgMigrateContract".to_owned()))?; let tx_res = self .sign_and_broadcast(sender_address, vec![migrate_msg], fee, memo) @@ -305,7 +305,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { fee: Fee, memo: impl Into + Send + 'static, funds: Vec, - ) -> Result + ) -> Result where M: ?Sized + Serialize + Sync, { @@ -316,7 +316,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { funds: funds.into_iter().map(Into::into).collect(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgExecuteContract".to_owned()))?; let tx_res = self .sign_and_broadcast(sender_address, vec![execute_msg], fee, memo) @@ -340,7 +340,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { msgs: I, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result + ) -> Result where I: IntoIterator)> + Send, M: Serialize, @@ -355,7 +355,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { funds: funds.into_iter().map(Into::into).collect(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned())) + .map_err(|_| NyxdError::SerializationError("MsgExecuteContract".to_owned())) }) .collect::>()?; @@ -381,14 +381,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient { amount: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let send_msg = MsgSend { from_address: sender_address.clone(), to_address: recipient_address.clone(), amount: amount.into_iter().map(Into::into).collect(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgSend".to_owned()))?; self.sign_and_broadcast(sender_address, vec![send_msg], fee, memo) .await? @@ -401,7 +401,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { msgs: I, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result + ) -> Result where I: IntoIterator)> + Send, { @@ -414,7 +414,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { amount: amount.into_iter().map(Into::into).collect(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned())) + .map_err(|_| NyxdError::SerializationError("MsgExecuteContract".to_owned())) }) .collect::>()?; @@ -433,20 +433,20 @@ pub trait SigningCosmWasmClient: CosmWasmClient { allowed_messages: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let basic_allowance = BasicAllowance { spend_limit: spend_limit.into_iter().map(Into::into).collect(), expiration, } .to_any() - .map_err(|_| NymdError::SerializationError("BasicAllowance".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("BasicAllowance".to_owned()))?; let allowed_msg_allowance = AllowedMsgAllowance { allowance: Some(basic_allowance), allowed_messages, } .to_any() - .map_err(|_| NymdError::SerializationError("AllowedMsgAllowance".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("AllowedMsgAllowance".to_owned()))?; let grant_allowance_msg = MsgGrantAllowance { granter: granter.to_owned(), @@ -454,7 +454,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { allowance: Some(allowed_msg_allowance), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgGrantAllowance".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgGrantAllowance".to_owned()))?; self.sign_and_broadcast(granter, vec![grant_allowance_msg], fee, memo) .await? @@ -467,13 +467,13 @@ pub trait SigningCosmWasmClient: CosmWasmClient { grantee: &AccountId, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let revoke_allowance_msg = MsgRevokeAllowance { granter: granter.to_owned(), grantee: grantee.to_owned(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgRevokeAllowance".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgRevokeAllowance".to_owned()))?; self.sign_and_broadcast(granter, vec![revoke_allowance_msg], fee, memo) .await? @@ -487,14 +487,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient { amount: Coin, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let delegate_msg = MsgDelegate { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), amount: amount.into(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgDelegate".to_owned()))?; self.sign_and_broadcast(delegator_address, vec![delegate_msg], fee, memo) .await? @@ -508,14 +508,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient { amount: Coin, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let undelegate_msg = MsgUndelegate { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), amount: amount.into(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgUndelegate".to_owned()))?; self.sign_and_broadcast(delegator_address, vec![undelegate_msg], fee, memo) .await? @@ -528,13 +528,13 @@ pub trait SigningCosmWasmClient: CosmWasmClient { validator_address: &AccountId, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let withdraw_msg = MsgWithdrawDelegatorReward { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), } .to_any() - .map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?; self.sign_and_broadcast(delegator_address, vec![withdraw_msg], fee, memo) .await? @@ -549,7 +549,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { messages: &[Any], fee: Fee, memo: &String, - ) -> Result { + ) -> Result { let auto_fee = |multiplier: Option| async move { debug!("Trying to simulate gas costs..."); // from what I've seen in manual testing, gas estimation does not exist if transaction @@ -558,7 +558,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { .simulate(signer_address, messages.to_vec(), memo.clone()) .await? .gas_info - .ok_or(NymdError::GasEstimationFailure)? + .ok_or(NyxdError::GasEstimationFailure)? .gas_used; let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER); @@ -569,7 +569,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { debug!("Final gas limit used: {}", gas); let fee = self.gas_price() * gas; - Ok::(tx::Fee::from_amount_and_gas(fee, gas)) + Ok::(tx::Fee::from_amount_and_gas(fee, gas)) }; let fee = match fee { Fee::Manual(fee) => fee, @@ -592,7 +592,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { messages: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let memo = memo.into(); let fee = self .determine_transaction_fee(signer_address, &messages, fee, &memo) @@ -600,7 +600,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { let tx_raw = self.sign(signer_address, messages, fee, memo).await?; let tx_bytes = tx_raw .to_bytes() - .map_err(|_| NymdError::SerializationError("Tx".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("Tx".to_owned()))?; CosmWasmClient::broadcast_tx_async(self, tx_bytes.into()).await } @@ -612,7 +612,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { messages: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let memo = memo.into(); let fee = self .determine_transaction_fee(signer_address, &messages, fee, &memo) @@ -620,7 +620,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { let tx_raw = self.sign(signer_address, messages, fee, memo).await?; let tx_bytes = tx_raw .to_bytes() - .map_err(|_| NymdError::SerializationError("Tx".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("Tx".to_owned()))?; CosmWasmClient::broadcast_tx_sync(self, tx_bytes.into()).await } @@ -632,7 +632,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { messages: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let memo = memo.into(); let fee = self .determine_transaction_fee(signer_address, &messages, fee, &memo) @@ -641,7 +641,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { let tx_raw = self.sign(signer_address, messages, fee, memo).await?; let tx_bytes = tx_raw .to_bytes() - .map_err(|_| NymdError::SerializationError("Tx".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("Tx".to_owned()))?; CosmWasmClient::broadcast_tx_commit(self, tx_bytes.into()).await } @@ -653,7 +653,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { messages: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let memo = memo.into(); let fee = self .determine_transaction_fee(signer_address, &messages, fee, &memo) @@ -662,7 +662,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { let tx_raw = self.sign(signer_address, messages, fee, memo).await?; let tx_bytes = tx_raw .to_bytes() - .map_err(|_| NymdError::SerializationError("Tx".to_owned()))?; + .map_err(|_| NyxdError::SerializationError("Tx".to_owned()))?; self.broadcast_tx(tx_bytes.into()).await } @@ -674,12 +674,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient { fee: tx::Fee, memo: impl Into + Send + 'static, signer_data: SignerData, - ) -> Result { + ) -> Result { let signer_accounts = self.signer().try_derive_accounts()?; let account_from_signer = signer_accounts .iter() .find(|account| &account.address == signer_address) - .ok_or_else(|| NymdError::SigningAccountNotFound(signer_address.clone()))?; + .ok_or_else(|| NyxdError::SigningAccountNotFound(signer_address.clone()))?; // TODO: WTF HOW IS TIMEOUT_HEIGHT SUPPOSED TO GET DETERMINED? // IT DOESNT EXIST IN COSMJS!! @@ -691,7 +691,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { SignerInfo::single_direct(Some(account_from_signer.public_key), signer_data.sequence); let auth_info = signer_info.auth_info(fee); - // ideally I'd prefer to have the entire error put into the NymdError::SigningFailure + // ideally I'd prefer to have the entire error put into the NyxdError::SigningFailure // but I'm super hesitant to trying to downcast the eyre::Report to cosmrs::error::Error let sign_doc = SignDoc::new( &tx_body, @@ -699,7 +699,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { &signer_data.chain_id, signer_data.account_number, ) - .map_err(|_| NymdError::SigningFailure)?; + .map_err(|_| NyxdError::SigningFailure)?; self.signer() .sign_direct_with_account(account_from_signer, sign_doc) @@ -711,7 +711,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { messages: Vec, fee: tx::Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { // TODO: Future optimisation: rather than grabbing current account_number and sequence // on every sign request -> just keep them cached on the struct and increment as required let sequence_response = self.get_sequence(signer_address).await?; @@ -742,7 +742,7 @@ impl Client { endpoint: U, signer: DirectSecp256k1HdWallet, gas_price: GasPrice, - ) -> Result + ) -> Result where U: TryInto, { @@ -756,6 +756,19 @@ impl Client { }) } + pub fn change_endpoint(&mut self, new_endpoint: U) -> Result<(), NyxdError> + where + U: TryInto, + { + let new_rpc_client = HttpClient::new(new_endpoint)?; + self.rpc_client = new_rpc_client; + Ok(()) + } + + pub fn into_signer(self) -> DirectSecp256k1HdWallet { + self.signer + } + pub fn set_broadcast_polling_rate(&mut self, broadcast_polling_rate: Duration) { self.broadcast_polling_rate = broadcast_polling_rate } diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/types.rs similarity index 92% rename from common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs rename to common/client-libs/validator-client/src/nyxd/cosmwasm_client/types.rs index b680a1a8f4..96260c3bd8 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/types.rs @@ -3,9 +3,9 @@ // TODO: There's a significant argument to pull those out of the package and make a PR on https://github.com/cosmos/cosmos-rust/ -use crate::nymd::cosmwasm_client::helpers::parse_proto_coin_vec; -use crate::nymd::cosmwasm_client::logs::Log; -use crate::nymd::error::NymdError; +use crate::nyxd::cosmwasm_client::helpers::parse_proto_coin_vec; +use crate::nyxd::cosmwasm_client::logs::Log; +use crate::nyxd::error::NyxdError; use cosmrs::crypto::PublicKey; use cosmrs::proto::cosmos::auth::v1beta1::{ BaseAccount as ProtoBaseAccount, ModuleAccount as ProtoModuleAccount, @@ -57,19 +57,19 @@ pub struct BaseAccount { } impl TryFrom for BaseAccount { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoBaseAccount) -> Result { let address: AccountId = value .address .parse() - .map_err(|_| NymdError::MalformedAccountAddress(value.address.clone()))?; + .map_err(|_| NyxdError::MalformedAccountAddress(value.address.clone()))?; let pubkey = value .pub_key .map(PublicKey::try_from) .transpose() - .map_err(|_| NymdError::InvalidPublicKey(address.clone()))?; + .map_err(|_| NyxdError::InvalidPublicKey(address.clone()))?; Ok(BaseAccount { address, @@ -89,7 +89,7 @@ pub struct ModuleAccount { } impl TryFrom for ModuleAccount { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoModuleAccount) -> Result { let base_account = value.base_account.map(TryFrom::try_from).transpose()?; @@ -114,7 +114,7 @@ pub struct BaseVestingAccount { } impl TryFrom for BaseVestingAccount { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoBaseVestingAccount) -> Result { let base_account = value.base_account.map(TryFrom::try_from).transpose()?; @@ -142,7 +142,7 @@ pub struct ContinuousVestingAccount { } impl TryFrom for ContinuousVestingAccount { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoContinuousVestingAccount) -> Result { let base_vesting_account = value @@ -166,7 +166,7 @@ pub struct DelayedVestingAccount { } impl TryFrom for DelayedVestingAccount { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoDelayedVestingAccount) -> Result { let base_vesting_account = value @@ -188,7 +188,7 @@ pub struct Period { } impl TryFrom for Period { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoPeriod) -> Result { Ok(Period { @@ -208,7 +208,7 @@ pub struct PeriodicVestingAccount { } impl TryFrom for PeriodicVestingAccount { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoPeriodicVestingAccount) -> Result { let base_vesting_account = value @@ -239,7 +239,7 @@ pub struct PermanentLockedAccount { } impl TryFrom for PermanentLockedAccount { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoPermanentLockedAccount) -> Result { let base_vesting_account = value @@ -265,43 +265,43 @@ pub enum Account { } impl Account { - pub fn try_get_base_account(&self) -> Result<&BaseAccount, NymdError> { + pub fn try_get_base_account(&self) -> Result<&BaseAccount, NyxdError> { match self { Account::Base(acc) => Ok(acc), Account::Module(acc) => acc .base_account .as_ref() - .ok_or(NymdError::NoBaseAccountInformationAvailable), + .ok_or(NyxdError::NoBaseAccountInformationAvailable), Account::BaseVesting(acc) => acc .base_account .as_ref() - .ok_or(NymdError::NoBaseAccountInformationAvailable), + .ok_or(NyxdError::NoBaseAccountInformationAvailable), Account::ContinuousVesting(acc) => acc .base_vesting_account .as_ref() .and_then(|vesting_acc| vesting_acc.base_account.as_ref()) - .ok_or(NymdError::NoBaseAccountInformationAvailable), + .ok_or(NyxdError::NoBaseAccountInformationAvailable), Account::DelayedVesting(acc) => acc .base_vesting_account .as_ref() .and_then(|vesting_acc| vesting_acc.base_account.as_ref()) - .ok_or(NymdError::NoBaseAccountInformationAvailable), + .ok_or(NyxdError::NoBaseAccountInformationAvailable), Account::PeriodicVesting(acc) => acc .base_vesting_account .as_ref() .and_then(|vesting_acc| vesting_acc.base_account.as_ref()) - .ok_or(NymdError::NoBaseAccountInformationAvailable), + .ok_or(NyxdError::NoBaseAccountInformationAvailable), Account::PermanentLockedVesting(acc) => acc .base_vesting_account .as_ref() .and_then(|vesting_acc| vesting_acc.base_account.as_ref()) - .ok_or(NymdError::NoBaseAccountInformationAvailable), + .ok_or(NyxdError::NoBaseAccountInformationAvailable), } } } impl TryFrom for Account { - type Error = NymdError; + type Error = NyxdError; fn try_from(raw_account: Any) -> Result { match raw_account.type_url.as_ref() { @@ -328,7 +328,7 @@ impl TryFrom for Account { ProtoPermanentLockedAccount::decode(raw_account.value.as_ref())?.try_into()?, )) } - _ => Err(NymdError::UnsupportedAccountType { + _ => Err(NyxdError::UnsupportedAccountType { type_url: raw_account.type_url, }), } @@ -347,7 +347,7 @@ pub struct Code { } impl TryFrom for Code { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: CodeInfoResponse) -> Result { let CodeInfoResponse { @@ -358,7 +358,7 @@ impl TryFrom for Code { let creator = creator .parse() - .map_err(|_| NymdError::MalformedAccountAddress(creator))?; + .map_err(|_| NyxdError::MalformedAccountAddress(creator))?; Ok(Code { code_id, @@ -391,7 +391,7 @@ pub(crate) struct ContractInfo { } impl TryFrom for ContractInfo { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoContractInfo) -> Result { let ProtoContractInfo { @@ -408,7 +408,7 @@ impl TryFrom for ContractInfo { Some( admin .parse() - .map_err(|_| NymdError::MalformedAccountAddress(admin))?, + .map_err(|_| NyxdError::MalformedAccountAddress(admin))?, ) }; @@ -416,7 +416,7 @@ impl TryFrom for ContractInfo { code_id, creator: creator .parse() - .map_err(|_| NymdError::MalformedAccountAddress(creator))?, + .map_err(|_| NyxdError::MalformedAccountAddress(creator))?, admin, label, }) @@ -466,14 +466,14 @@ pub struct ContractCodeHistoryEntry { } impl TryFrom for ContractCodeHistoryEntry { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoContractCodeHistoryEntry) -> Result { let operation = match ContractCodeHistoryOperationType::from_i32(value.operation) - .ok_or(NymdError::InvalidContractHistoryOperation)? + .ok_or(NyxdError::InvalidContractHistoryOperation)? { ContractCodeHistoryOperationType::Unspecified => { - return Err(NymdError::InvalidContractHistoryOperation) + return Err(NyxdError::InvalidContractHistoryOperation) } ContractCodeHistoryOperationType::Init => ContractCodeHistoryEntryOperation::Init, ContractCodeHistoryOperationType::Genesis => ContractCodeHistoryEntryOperation::Genesis, @@ -484,7 +484,7 @@ impl TryFrom for ContractCodeHistoryEntry { operation, code_id: value.code_id, msg_json: String::from_utf8(value.msg) - .map_err(|_| NymdError::DeserializationError("Contract history msg".to_owned()))?, + .map_err(|_| NyxdError::DeserializationError("Contract history msg".to_owned()))?, }) } } @@ -532,7 +532,7 @@ pub struct AbciResult { } impl TryFrom for AbciResult { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoAbciResult) -> Result { let mut events = Vec::with_capacity(value.events.len()); @@ -543,9 +543,9 @@ impl TryFrom for AbciResult { let mut attributes = Vec::with_capacity(proto_event.attributes.len()); for proto_attribute in proto_event.attributes.into_iter() { let stringified_ked = String::from_utf8(proto_attribute.key) - .map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?; + .map_err(|_| NyxdError::DeserializationError("EventAttributeKey".to_owned()))?; let stringified_value = String::from_utf8(proto_attribute.value) - .map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?; + .map_err(|_| NyxdError::DeserializationError("EventAttributeKey".to_owned()))?; attributes.push(abci::tag::Tag { key: stringified_ked.parse().unwrap(), @@ -574,7 +574,7 @@ pub struct SimulateResponse { } impl TryFrom for SimulateResponse { - type Error = NymdError; + type Error = NyxdError; fn try_from(value: ProtoSimulateResponse) -> Result { Ok(SimulateResponse { diff --git a/common/client-libs/validator-client/src/nymd/error.rs b/common/client-libs/validator-client/src/nyxd/error.rs similarity index 89% rename from common/client-libs/validator-client/src/nymd/error.rs rename to common/client-libs/validator-client/src/nyxd/error.rs index 1ebaa4e020..2330edbdf7 100644 --- a/common/client-libs/validator-client/src/nymd/error.rs +++ b/common/client-libs/validator-client/src/nyxd/error.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::cosmwasm_client::types::ContractCodeId; +use crate::nyxd::cosmwasm_client::types::ContractCodeId; use cosmrs::{ bip32, rpc::endpoint::abci_query::AbciQuery, @@ -21,7 +21,7 @@ pub use cosmrs::rpc::{ }; #[derive(Debug, Error)] -pub enum NymdError { +pub enum NyxdError { #[error("No contract address is available to perform the call")] NoContractAddressAvailable, @@ -46,25 +46,25 @@ pub enum NymdError { #[error("{0} is not a valid tx hash")] InvalidTxHash(String), - #[error("There was an issue with a tendermint RPC request - {0}")] + #[error("Tendermint RPC request failed - {0}")] TendermintError(#[from] TendermintRpcError), - #[error("There was an issue when attempting to serialize data ({0})")] + #[error("Failed when attempting to serialize data ({0})")] SerializationError(String), - #[error("There was an issue when attempting to deserialize data ({0})")] + #[error("Failed when attempting to deserialize data ({0})")] DeserializationError(String), - #[error("There was an issue when attempting to encode our protobuf data - {0}")] + #[error("Failed when attempting to encode our protobuf data - {0}")] ProtobufEncodingError(#[from] prost::EncodeError), - #[error("There was an issue when attempting to decode our protobuf data - {0}")] + #[error("Failed to decode our protobuf data - {0}")] ProtobufDecodingError(#[from] prost::DecodeError), #[error("Account {0} does not exist on the chain")] NonExistentAccountError(AccountId), - #[error("There was an issue with the serialization/deserialization - {0}")] + #[error("Failed on json serialization/deserialization - {0}")] SerdeJsonError(#[from] serde_json::Error), #[error("Account {0} is not a valid account address")] @@ -140,7 +140,7 @@ pub enum NymdError { CosmwasmStdError(#[from] cosmwasm_std::StdError), #[error("Coconut interface error: {0}")] - CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), + CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), #[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")] UnexpectedBech32Prefix { got: String, expected: String }, @@ -148,10 +148,10 @@ pub enum NymdError { // The purpose of parsing the abci query result is that we want to generate the `pretty_log` if // possible. -pub fn parse_abci_query_result(query_result: AbciQuery) -> Result { +pub fn parse_abci_query_result(query_result: AbciQuery) -> Result { match query_result.code { AbciCode::Ok => Ok(query_result), - AbciCode::Err(code) => Err(NymdError::AbciError { + AbciCode::Err(code) => Err(NyxdError::AbciError { code, log: query_result.log.clone(), pretty_log: try_parse_abci_log(&query_result.log), @@ -172,10 +172,10 @@ fn try_parse_abci_log(log: &abci::Log) -> Option { } } -impl NymdError { +impl NyxdError { pub fn is_tendermint_response_timeout(&self) -> bool { match &self { - NymdError::TendermintError(TendermintRpcError( + NyxdError::TendermintError(TendermintRpcError( TendermintRpcErrorDetail::Response(err), _, )) => { @@ -202,7 +202,7 @@ impl NymdError { pub fn is_tendermint_response_duplicate(&self) -> bool { match &self { - NymdError::TendermintError(TendermintRpcError( + NyxdError::TendermintError(TendermintRpcError( TendermintRpcErrorDetail::Response(err), _, )) => { diff --git a/common/client-libs/validator-client/src/nymd/fee/gas_price.rs b/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs similarity index 96% rename from common/client-libs/validator-client/src/nymd/fee/gas_price.rs rename to common/client-libs/validator-client/src/nyxd/fee/gas_price.rs index c79015ff22..10ae3a7bb7 100644 --- a/common/client-libs/validator-client/src/nymd/fee/gas_price.rs +++ b/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs @@ -1,11 +1,11 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::error::NymdError; -use config::defaults; +use crate::nyxd::error::NyxdError; use cosmrs::tx::Gas; use cosmrs::Coin; use cosmwasm_std::{Decimal, Fraction, Uint128}; +use nym_config::defaults; use std::ops::Mul; use std::str::FromStr; @@ -55,7 +55,7 @@ impl<'a> Mul for &'a GasPrice { } impl FromStr for GasPrice { - type Err = NymdError; + type Err = NyxdError; fn from_str(s: &str) -> Result { let possible_amount = s @@ -65,7 +65,7 @@ impl FromStr for GasPrice { let amount_len = possible_amount.len(); let amount = possible_amount .parse() - .map_err(|_| NymdError::MalformedGasPrice)?; + .map_err(|_| NyxdError::MalformedGasPrice)?; let possible_denom = s.chars().skip(amount_len).collect::(); let denom = possible_denom.trim().to_string(); @@ -74,7 +74,7 @@ impl FromStr for GasPrice { } impl GasPrice { - pub fn new_with_default_price(denom: &str) -> Result { + pub fn new_with_default_price(denom: &str) -> Result { format!("{}{}", defaults::GAS_PRICE_AMOUNT, denom).parse() } } diff --git a/common/client-libs/validator-client/src/nymd/fee/mod.rs b/common/client-libs/validator-client/src/nyxd/fee/mod.rs similarity index 96% rename from common/client-libs/validator-client/src/nymd/fee/mod.rs rename to common/client-libs/validator-client/src/nyxd/fee/mod.rs index 411cac2365..e1ee4b4d5c 100644 --- a/common/client-libs/validator-client/src/nymd/fee/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/fee/mod.rs @@ -1,8 +1,8 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::Coin; -use crate::nymd::Gas; +use crate::nyxd::Gas; +use crate::nyxd::{Coin, GasPrice}; use cosmrs::{tx, AccountId}; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; @@ -64,6 +64,12 @@ impl Display for Fee { } impl Fee { + pub fn manual_with_gas_price(fee: Coin, gas_price: GasPrice) -> Self { + let gas_limit = &fee / gas_price; + + Fee::Manual(tx::Fee::from_amount_and_gas(fee.into(), gas_limit)) + } + pub fn new_payer_granter_auto( gas_adjustment: Option, payer: Option, diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs similarity index 84% rename from common/client-libs/validator-client/src/nymd/mod.rs rename to common/client-libs/validator-client/src/nyxd/mod.rs index dabe4b0819..4990f1b030 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -1,35 +1,38 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::cosmwasm_client::signing_client; -use crate::nymd::cosmwasm_client::types::{ +use crate::nyxd::cosmwasm_client::signing_client; +use crate::nyxd::cosmwasm_client::types::{ Account, ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions, InstantiateResult, MigrateResult, SequenceResponse, SimulateResponse, UploadResult, }; -use crate::nymd::error::NymdError; -use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER; -use crate::nymd::wallet::DirectSecp256k1HdWallet; +use crate::nyxd::error::NyxdError; +use crate::nyxd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER; +use crate::nyxd::wallet::DirectSecp256k1HdWallet; use cosmrs::cosmwasm; use cosmrs::rpc::endpoint::block::Response as BlockResponse; use cosmrs::rpc::query::Query; use cosmrs::rpc::Error as TendermintRpcError; use cosmrs::rpc::HttpClientUrl; use cosmrs::tx::Msg; -use execute::execute; -use network_defaults::{ChainDetails, NymNetworkDetails}; +use log::debug; +use nym_execute::execute; +use nym_mixnet_contract_common::MixId; +use nym_network_defaults::{ChainDetails, NymNetworkDetails}; +use nym_vesting_contract_common::ExecuteMsg as VestingExecuteMsg; +use nym_vesting_contract_common::PledgeCap; use serde::{Deserialize, Serialize}; use std::convert::TryInto; use std::time::SystemTime; -use vesting_contract_common::ExecuteMsg as VestingExecuteMsg; -pub use crate::nymd::cosmwasm_client::client::CosmWasmClient; -pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; -pub use crate::nymd::fee::Fee; +pub use crate::nyxd::cosmwasm_client::client::CosmWasmClient; +pub use crate::nyxd::cosmwasm_client::signing_client::SigningCosmWasmClient; +pub use crate::nyxd::fee::Fee; pub use coin::Coin; pub use cosmrs::bank::MsgSend; pub use cosmrs::rpc::endpoint::tx::Response as TxResponse; pub use cosmrs::rpc::endpoint::validators::Response as ValidatorResponse; -pub use cosmrs::rpc::HttpClient as QueryNymdClient; +pub use cosmrs::rpc::HttpClient as QueryNyxdClient; pub use cosmrs::rpc::Paging; pub use cosmrs::tendermint::abci::responses::{DeliverTx, Event}; pub use cosmrs::tendermint::abci::tag::Tag; @@ -42,10 +45,8 @@ pub use cosmrs::Coin as CosmosCoin; pub use cosmrs::{bip32, AccountId, Decimal, Denom}; pub use cosmwasm_std::Coin as CosmWasmCoin; pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment}; -use mixnet_contract_common::MixId; -pub use signing_client::Client as SigningNymdClient; +pub use signing_client::Client as SigningNyxdClient; pub use traits::{VestingQueryClient, VestingSigningClient}; -use vesting_contract_common::PledgeCap; pub mod coin; pub mod cosmwasm_client; @@ -64,6 +65,7 @@ pub struct Config { pub(crate) vesting_contract_address: Option, pub(crate) bandwidth_claim_contract_address: Option, pub(crate) coconut_bandwidth_contract_address: Option, + pub(crate) group_contract_address: Option, pub(crate) multisig_contract_address: Option, pub(crate) coconut_dkg_contract_address: Option, // TODO: add this in later commits @@ -74,13 +76,16 @@ impl Config { fn parse_optional_account( raw: Option<&String>, expected_prefix: &str, - ) -> Result, NymdError> { + ) -> Result, NyxdError> { if let Some(address) = raw { + debug!("Raw address:{:?}", raw); + debug!("Expected prefix:{:?}", expected_prefix); let parsed: AccountId = address .parse() - .map_err(|_| NymdError::MalformedAccountAddress(address.clone()))?; + .map_err(|_| NyxdError::MalformedAccountAddress(address.clone()))?; + debug!("Parsed prefix:{:?}", parsed); if parsed.prefix() != expected_prefix { - Err(NymdError::UnexpectedBech32Prefix { + Err(NyxdError::UnexpectedBech32Prefix { got: parsed.prefix().into(), expected: expected_prefix.into(), }) @@ -92,7 +97,7 @@ impl Config { } } - pub fn try_from_nym_network_details(details: &NymNetworkDetails) -> Result { + pub fn try_from_nym_network_details(details: &NymNetworkDetails) -> Result { let prefix = &details.chain_details.bech32_account_prefix; Ok(Config { chain_details: details.chain_details.clone(), @@ -115,6 +120,10 @@ impl Config { .as_ref(), prefix, )?, + group_contract_address: Self::parse_optional_account( + details.contracts.group_contract_address.as_ref(), + prefix, + )?, multisig_contract_address: Self::parse_optional_account( details.contracts.multisig_contract_address.as_ref(), prefix, @@ -127,21 +136,21 @@ impl Config { } } -#[derive(Debug)] -pub struct NymdClient { +#[derive(Clone, Debug)] +pub struct NyxdClient { client: C, config: Config, client_address: Option>, simulated_gas_multiplier: f32, } -impl NymdClient { - pub fn connect(config: Config, endpoint: U) -> Result, NymdError> +impl NyxdClient { + pub fn connect(config: Config, endpoint: U) -> Result, NyxdError> where U: TryInto, { - Ok(NymdClient { - client: QueryNymdClient::new(endpoint)?, + Ok(NyxdClient { + client: QueryNyxdClient::new(endpoint)?, config, client_address: None, simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER, @@ -149,15 +158,15 @@ impl NymdClient { } } -impl NymdClient { +impl NyxdClient { // maybe the wallet could be made into a generic, but for now, let's just have this one implementation pub fn connect_with_signer( config: Config, - network: config::defaults::NymNetworkDetails, + network: nym_config::defaults::NymNetworkDetails, endpoint: U, signer: DirectSecp256k1HdWallet, gas_price: Option, - ) -> Result, NymdError> + ) -> Result, NyxdError> where U: TryInto, { @@ -169,8 +178,8 @@ impl NymdClient { .collect(); let gas_price = gas_price.unwrap_or(GasPrice::new_with_default_price(&denom)?); - Ok(NymdClient { - client: SigningNymdClient::connect_with_signer(endpoint, signer, gas_price)?, + Ok(NyxdClient { + client: SigningNyxdClient::connect_with_signer(endpoint, signer, gas_price)?, config, client_address: Some(client_address), simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER, @@ -182,13 +191,13 @@ impl NymdClient { endpoint: U, mnemonic: bip39::Mnemonic, gas_price: Option, - ) -> Result, NymdError> + ) -> Result, NyxdError> where U: TryInto, { let prefix = &config.chain_details.bech32_account_prefix; let denom = &config.chain_details.mix_denom.base; - let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)?; + let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); let client_address = wallet .try_derive_accounts()? .into_iter() @@ -196,16 +205,30 @@ impl NymdClient { .collect(); let gas_price = gas_price.unwrap_or(GasPrice::new_with_default_price(denom)?); - Ok(NymdClient { - client: SigningNymdClient::connect_with_signer(endpoint, wallet, gas_price)?, + Ok(NyxdClient { + client: SigningNyxdClient::connect_with_signer(endpoint, wallet, gas_price)?, config, client_address: Some(client_address), simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER, }) } + + pub fn change_endpoint(&mut self, new_endpoint: U) -> Result<(), NyxdError> + where + U: TryInto, + { + self.client.change_endpoint(new_endpoint) + } + + pub fn into_signer(self) -> DirectSecp256k1HdWallet { + self.client.into_signer() + } } -impl NymdClient { +impl NyxdClient +where + C: Clone, +{ pub fn current_config(&self) -> &Config { &self.config } @@ -234,7 +257,7 @@ impl NymdClient { self.config.multisig_contract_address = Some(address); } - // TODO: this should get changed into Result<&AccountId, NymdError> (or Option<&AccountId> in future commits + // TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits // note: what unwrap is doing here is just moving a failure that would have normally // occurred in `connect` when attempting to parse an empty address, // so it's not introducing new source of failure (just moves it) @@ -242,7 +265,7 @@ impl NymdClient { self.config.mixnet_contract_address.as_ref().unwrap() } - // TODO: this should get changed into Result<&AccountId, NymdError> (or Option<&AccountId> in future commits + // TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits // note: what unwrap is doing here is just moving a failure that would have normally // occurred in `connect` when attempting to parse an empty address, // so it's not introducing new source of failure (just moves it) @@ -250,7 +273,7 @@ impl NymdClient { self.config.vesting_contract_address.as_ref().unwrap() } - // TODO: this should get changed into Result<&AccountId, NymdError> (or Option<&AccountId> in future commits + // TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits // note: what unwrap is doing here is just moving a failure that would have normally // occurred in `connect` when attempting to parse an empty address, // so it's not introducing new source of failure (just moves it) @@ -261,7 +284,7 @@ impl NymdClient { .unwrap() } - // TODO: this should get changed into Result<&AccountId, NymdError> (or Option<&AccountId> in future commits + // TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits // note: what unwrap is doing here is just moving a failure that would have normally // occurred in `connect` when attempting to parse an empty address, // so it's not introducing new source of failure (just moves it) @@ -272,7 +295,11 @@ impl NymdClient { .unwrap() } - // TODO: this should get changed into Result<&AccountId, NymdError> (or Option<&AccountId> in future commits + pub fn group_contract_address(&self) -> &AccountId { + self.config.group_contract_address.as_ref().unwrap() + } + + // TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits // note: what unwrap is doing here is just moving a failure that would have normally // occurred in `connect` when attempting to parse an empty address, // so it's not introducing new source of failure (just moves it) @@ -280,7 +307,7 @@ impl NymdClient { self.config.multisig_contract_address.as_ref().unwrap() } - // TODO: this should get changed into Result<&AccountId, NymdError> (or Option<&AccountId> in future commits + // TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits // note: what unwrap is doing here is just moving a failure that would have normally // occurred in `connect` when attempting to parse an empty address, // so it's not introducing new source of failure (just moves it) @@ -296,7 +323,7 @@ impl NymdClient { &self, contract: &AccountId, query_msg: &M, - ) -> Result + ) -> Result where C: CosmWasmClient + Sync, M: ?Sized + Serialize + Sync, @@ -309,7 +336,7 @@ impl NymdClient { &self, contract: &AccountId, query_data: Vec, - ) -> Result, NymdError> + ) -> Result, NyxdError> where C: CosmWasmClient + Sync, { @@ -321,7 +348,7 @@ impl NymdClient { contract_address: &AccountId, msg: &M, funds: Vec, - ) -> Result + ) -> Result where C: SigningCosmWasmClient, M: ?Sized + Serialize, @@ -366,7 +393,7 @@ impl NymdClient { // CHAIN QUERIES - pub async fn account_sequence(&self) -> Result + pub async fn account_sequence(&self) -> Result where C: SigningCosmWasmClient + Sync, { @@ -376,7 +403,7 @@ impl NymdClient { pub async fn get_account_details( &self, address: &AccountId, - ) -> Result, NymdError> + ) -> Result, NyxdError> where C: CosmWasmClient + Sync, { @@ -386,7 +413,7 @@ impl NymdClient { pub async fn get_account_public_key( &self, address: &AccountId, - ) -> Result, NymdError> + ) -> Result, NyxdError> where C: CosmWasmClient + Sync, { @@ -398,7 +425,7 @@ impl NymdClient { Ok(None) } - pub async fn get_current_block_timestamp(&self) -> Result + pub async fn get_current_block_timestamp(&self) -> Result where C: CosmWasmClient + Sync, { @@ -408,21 +435,21 @@ impl NymdClient { pub async fn get_block_timestamp( &self, height: Option, - ) -> Result + ) -> Result where C: CosmWasmClient + Sync, { Ok(self.client.get_block(height).await?.block.header.time) } - pub async fn get_block(&self, height: Option) -> Result + pub async fn get_block(&self, height: Option) -> Result where C: CosmWasmClient + Sync, { self.client.get_block(height).await } - pub async fn get_current_block_height(&self) -> Result + pub async fn get_current_block_height(&self) -> Result where C: CosmWasmClient + Sync, { @@ -434,7 +461,7 @@ impl NymdClient { /// # Arguments /// /// * `height`: height of the block for which we want to obtain the hash. - pub async fn get_block_hash(&self, height: u32) -> Result + pub async fn get_block_hash(&self, height: u32) -> Result where C: CosmWasmClient + Sync, { @@ -448,7 +475,7 @@ impl NymdClient { &self, height: u64, paging: Paging, - ) -> Result + ) -> Result where C: CosmWasmClient + Sync, { @@ -459,42 +486,42 @@ impl NymdClient { &self, address: &AccountId, denom: String, - ) -> Result, NymdError> + ) -> Result, NyxdError> where C: CosmWasmClient + Sync, { self.client.get_balance(address, denom).await } - pub async fn get_all_balances(&self, address: &AccountId) -> Result, NymdError> + pub async fn get_all_balances(&self, address: &AccountId) -> Result, NyxdError> where C: CosmWasmClient + Sync, { self.client.get_all_balances(address).await } - pub async fn get_tx(&self, id: tx::Hash) -> Result + pub async fn get_tx(&self, id: tx::Hash) -> Result where C: CosmWasmClient + Sync, { self.client.get_tx(id).await } - pub async fn search_tx(&self, query: Query) -> Result, NymdError> + pub async fn search_tx(&self, query: Query) -> Result, NyxdError> where C: CosmWasmClient + Sync, { self.client.search_tx(query).await } - pub async fn get_total_supply(&self) -> Result, NymdError> + pub async fn get_total_supply(&self) -> Result, NyxdError> where C: CosmWasmClient + Sync, { self.client.get_total_supply().await } - pub async fn simulate(&self, messages: I) -> Result + pub async fn simulate(&self, messages: I) -> Result where C: SigningCosmWasmClient + Sync, I: IntoIterator + Send, @@ -508,7 +535,7 @@ impl NymdClient { .map(|msg| msg.into_any()) .collect::, _>>() .map_err(|_| { - NymdError::SerializationError("custom simulate messages".to_owned()) + NyxdError::SerializationError("custom simulate messages".to_owned()) })?, "simulating execution of transactions", ) @@ -522,7 +549,7 @@ impl NymdClient { amount: Vec, memo: impl Into + Send + 'static, fee: Option, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -538,7 +565,7 @@ impl NymdClient { msgs: Vec<(AccountId, Vec)>, memo: impl Into + Send + 'static, fee: Option, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -557,7 +584,7 @@ impl NymdClient { allowed_messages: Vec, memo: impl Into + Send + 'static, fee: Option, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -581,7 +608,7 @@ impl NymdClient { grantee: &AccountId, memo: impl Into + Send + 'static, fee: Option, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -598,7 +625,7 @@ impl NymdClient { fee: Option, memo: impl Into + Send + 'static, funds: Vec, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, M: ?Sized + Serialize + Sync, @@ -615,7 +642,7 @@ impl NymdClient { msgs: I, fee: Option, memo: impl Into + Send + 'static, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, I: IntoIterator)> + Send, @@ -632,7 +659,7 @@ impl NymdClient { wasm_code: Vec, memo: impl Into + Send + 'static, fee: Option, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -650,7 +677,7 @@ impl NymdClient { memo: impl Into + Send + 'static, options: Option, fee: Option, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, M: ?Sized + Serialize + Sync, @@ -667,7 +694,7 @@ impl NymdClient { new_admin: &AccountId, memo: impl Into + Send + 'static, fee: Option, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -682,7 +709,7 @@ impl NymdClient { contract_address: &AccountId, memo: impl Into + Send + 'static, fee: Option, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -699,7 +726,7 @@ impl NymdClient { msg: &M, memo: impl Into + Send + 'static, fee: Option, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, M: ?Sized + Serialize + Sync, diff --git a/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_query_client.rs similarity index 59% rename from common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_query_client.rs rename to common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_query_client.rs index b2e38e12a5..71758a124b 100644 --- a/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_query_client.rs @@ -1,11 +1,11 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::error::NymdError; -use crate::nymd::{CosmWasmClient, NymdClient}; +use crate::nyxd::error::NyxdError; +use crate::nyxd::{CosmWasmClient, NyxdClient}; -use coconut_bandwidth_contract_common::msg::QueryMsg; -use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; +use nym_coconut_bandwidth_contract_common::msg::QueryMsg; +use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; use async_trait::async_trait; @@ -14,15 +14,15 @@ pub trait CoconutBandwidthQueryClient { async fn get_spent_credential( &self, blinded_serial_number: String, - ) -> Result; + ) -> Result; } #[async_trait] -impl CoconutBandwidthQueryClient for NymdClient { +impl CoconutBandwidthQueryClient for NyxdClient { async fn get_spent_credential( &self, blinded_serial_number: String, - ) -> Result { + ) -> Result { let request = QueryMsg::GetSpentCredential { blinded_serial_number, }; diff --git a/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_signing_client.rs similarity index 75% rename from common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs rename to common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_signing_client.rs index 0fc0d0e126..b21ce7543e 100644 --- a/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/coconut_bandwidth_signing_client.rs @@ -1,12 +1,12 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; -use crate::nymd::cosmwasm_client::types::ExecuteResult; -use crate::nymd::error::NymdError; -use crate::nymd::{Coin, Fee, NymdClient}; -use coconut_bandwidth_contract_common::spend_credential::SpendCredentialData; -use coconut_bandwidth_contract_common::{deposit::DepositData, msg::ExecuteMsg}; +pub use crate::nyxd::cosmwasm_client::signing_client::SigningCosmWasmClient; +use crate::nyxd::cosmwasm_client::types::ExecuteResult; +use crate::nyxd::error::NyxdError; +use crate::nyxd::{Coin, Fee, NyxdClient}; +use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialData; +use nym_coconut_bandwidth_contract_common::{deposit::DepositData, msg::ExecuteMsg}; use async_trait::async_trait; @@ -19,18 +19,20 @@ pub trait CoconutBandwidthSigningClient { verification_key: String, encryption_key: String, fee: Option, - ) -> Result; + ) -> Result; async fn spend_credential( &self, funds: Coin, blinded_serial_number: String, gateway_cosmos_address: String, fee: Option, - ) -> Result; + ) -> Result; } #[async_trait] -impl CoconutBandwidthSigningClient for NymdClient { +impl CoconutBandwidthSigningClient + for NyxdClient +{ async fn deposit( &self, amount: Coin, @@ -38,7 +40,7 @@ impl CoconutBandwidthSigningClient for N verification_key: String, encryption_key: String, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = ExecuteMsg::DepositFunds { data: DepositData::new(info.to_string(), verification_key, encryption_key), @@ -60,7 +62,7 @@ impl CoconutBandwidthSigningClient for N blinded_serial_number: String, gateway_cosmos_address: String, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = ExecuteMsg::SpendCredential { data: SpendCredentialData::new( diff --git a/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/dkg_query_client.rs similarity index 67% rename from common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs rename to common/client-libs/validator-client/src/nyxd/traits/dkg_query_client.rs index b67627d316..20e032dff0 100644 --- a/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/dkg_query_client.rs @@ -1,70 +1,80 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::error::NymdError; -use crate::nymd::{CosmWasmClient, NymdClient}; +use crate::nyxd::error::NyxdError; +use crate::nyxd::{CosmWasmClient, NyxdClient}; use async_trait::async_trait; -use coconut_dkg_common::dealer::{ +use cosmrs::AccountId; +use nym_coconut_dkg_common::dealer::{ DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse, }; -use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; -use coconut_dkg_common::types::EpochState; -use coconut_dkg_common::verification_key::PagedVKSharesResponse; -use cosmrs::AccountId; +use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; +use nym_coconut_dkg_common::types::{Epoch, EpochId, InitialReplacementData}; +use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse; #[async_trait] pub trait DkgQueryClient { - async fn get_current_epoch_state(&self) -> Result; - async fn get_current_epoch_threshold(&self) -> Result, NymdError>; + async fn get_current_epoch(&self) -> Result; + async fn get_current_epoch_threshold(&self) -> Result, NyxdError>; + async fn get_initial_dealers(&self) -> Result, NyxdError>; async fn get_dealer_details( &self, address: &AccountId, - ) -> Result; + ) -> Result; async fn get_current_dealers_paged( &self, start_after: Option, page_limit: Option, - ) -> Result; + ) -> Result; async fn get_past_dealers_paged( &self, start_after: Option, page_limit: Option, - ) -> Result; + ) -> Result; async fn get_dealings_paged( &self, idx: usize, start_after: Option, page_limit: Option, - ) -> Result; + ) -> Result; async fn get_vk_shares_paged( &self, + epoch_id: EpochId, start_after: Option, page_limit: Option, - ) -> Result; + ) -> Result; } #[async_trait] -impl DkgQueryClient for NymdClient +impl DkgQueryClient for NyxdClient where - C: CosmWasmClient + Send + Sync, + C: CosmWasmClient + Send + Sync + Clone, { - async fn get_current_epoch_state(&self) -> Result { + async fn get_current_epoch(&self) -> Result { let request = DkgQueryMsg::GetCurrentEpochState {}; self.client .query_contract_smart(self.coconut_dkg_contract_address(), &request) .await } - async fn get_current_epoch_threshold(&self) -> Result, NymdError> { + async fn get_current_epoch_threshold(&self) -> Result, NyxdError> { let request = DkgQueryMsg::GetCurrentEpochThreshold {}; self.client .query_contract_smart(self.coconut_dkg_contract_address(), &request) .await } + + async fn get_initial_dealers(&self) -> Result, NyxdError> { + let request = DkgQueryMsg::GetInitialDealers {}; + self.client + .query_contract_smart(self.coconut_dkg_contract_address(), &request) + .await + } + async fn get_dealer_details( &self, address: &AccountId, - ) -> Result { + ) -> Result { let request = DkgQueryMsg::GetDealerDetails { dealer_address: address.to_string(), }; @@ -77,7 +87,7 @@ where &self, start_after: Option, page_limit: Option, - ) -> Result { + ) -> Result { let request = DkgQueryMsg::GetCurrentDealers { start_after, limit: page_limit, @@ -91,7 +101,7 @@ where &self, start_after: Option, page_limit: Option, - ) -> Result { + ) -> Result { let request = DkgQueryMsg::GetPastDealers { start_after, limit: page_limit, @@ -106,7 +116,7 @@ where idx: usize, start_after: Option, page_limit: Option, - ) -> Result { + ) -> Result { let request = DkgQueryMsg::GetDealing { idx: idx as u64, limit: page_limit, @@ -119,10 +129,12 @@ where async fn get_vk_shares_paged( &self, + epoch_id: EpochId, start_after: Option, page_limit: Option, - ) -> Result { + ) -> Result { let request = DkgQueryMsg::GetVerificationKeys { + epoch_id, limit: page_limit, start_after, }; diff --git a/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/dkg_signing_client.rs similarity index 56% rename from common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs rename to common/client-libs/validator-client/src/nyxd/traits/dkg_signing_client.rs index b0d1dfaf08..b99260e120 100644 --- a/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/dkg_signing_client.rs @@ -1,51 +1,72 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::cosmwasm_client::types::ExecuteResult; -use crate::nymd::error::NymdError; -use crate::nymd::{Fee, NymdClient, SigningCosmWasmClient}; +use crate::nyxd::cosmwasm_client::types::ExecuteResult; +use crate::nyxd::error::NyxdError; +use crate::nyxd::{Fee, NyxdClient, SigningCosmWasmClient}; use async_trait::async_trait; -use coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg; -use coconut_dkg_common::types::EncodedBTEPublicKeyWithProof; -use coconut_dkg_common::verification_key::VerificationKeyShare; -use contracts_common::dealings::ContractSafeBytes; +use nym_coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg; +use nym_coconut_dkg_common::types::EncodedBTEPublicKeyWithProof; +use nym_coconut_dkg_common::verification_key::VerificationKeyShare; +use nym_contracts_common::dealings::ContractSafeBytes; #[async_trait] pub trait DkgSigningClient { + async fn advance_dkg_epoch_state(&self, fee: Option) -> Result; async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, announce_address: String, + resharing: bool, fee: Option, - ) -> Result; + ) -> Result; async fn submit_dealing_bytes( &self, commitment: ContractSafeBytes, + resharing: bool, fee: Option, - ) -> Result; + ) -> Result; async fn submit_verification_key_share( &self, share: VerificationKeyShare, + resharing: bool, fee: Option, - ) -> Result; + ) -> Result; } #[async_trait] -impl DkgSigningClient for NymdClient +impl DkgSigningClient for NyxdClient where - C: SigningCosmWasmClient + Send + Sync, + C: SigningCosmWasmClient + Send + Sync + Clone, { + async fn advance_dkg_epoch_state(&self, fee: Option) -> Result { + let req = DkgExecuteMsg::AdvanceEpochState {}; + + self.client + .execute( + self.address(), + self.coconut_dkg_contract_address(), + &req, + fee.unwrap_or_default(), + "advancing DKG state", + vec![], + ) + .await + } + async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, announce_address: String, + resharing: bool, fee: Option, - ) -> Result { + ) -> Result { let req = DkgExecuteMsg::RegisterDealer { bte_key_with_proof: bte_key, announce_address, + resharing, }; self.client @@ -63,9 +84,13 @@ where async fn submit_dealing_bytes( &self, dealing_bytes: ContractSafeBytes, + resharing: bool, fee: Option, - ) -> Result { - let req = DkgExecuteMsg::CommitDealing { dealing_bytes }; + ) -> Result { + let req = DkgExecuteMsg::CommitDealing { + dealing_bytes, + resharing, + }; self.client .execute( @@ -82,9 +107,10 @@ where async fn submit_verification_key_share( &self, share: VerificationKeyShare, + resharing: bool, fee: Option, - ) -> Result { - let req = DkgExecuteMsg::CommitVerificationKeyShare { share }; + ) -> Result { + let req = DkgExecuteMsg::CommitVerificationKeyShare { share, resharing }; self.client .execute( diff --git a/common/client-libs/validator-client/src/nyxd/traits/group_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/group_query_client.rs new file mode 100644 index 0000000000..f41fb2e261 --- /dev/null +++ b/common/client-libs/validator-client/src/nyxd/traits/group_query_client.rs @@ -0,0 +1,28 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::nyxd::error::NyxdError; +use crate::nyxd::{CosmWasmClient, NyxdClient}; + +use nym_group_contract_common::msg::QueryMsg; + +use async_trait::async_trait; +use cw4::MemberResponse; + +#[async_trait] +pub trait GroupQueryClient { + async fn member(&self, addr: String) -> Result; +} + +#[async_trait] +impl GroupQueryClient for NyxdClient { + async fn member(&self, addr: String) -> Result { + let request = QueryMsg::Member { + addr, + at_height: None, + }; + self.client + .query_contract_smart(self.group_contract_address(), &request) + .await + } +} diff --git a/common/client-libs/validator-client/src/nymd/traits/mixnet_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs similarity index 75% rename from common/client-libs/validator-client/src/nymd/traits/mixnet_query_client.rs rename to common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs index 8c6c2e8d41..2ff48d1113 100644 --- a/common/client-libs/validator-client/src/nymd/traits/mixnet_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/mixnet_query_client.rs @@ -1,66 +1,72 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub use crate::nymd::cosmwasm_client::client::CosmWasmClient; -use crate::nymd::error::NymdError; -use crate::nymd::NymdClient; +pub use crate::nyxd::cosmwasm_client::client::CosmWasmClient; +use crate::nyxd::error::NyxdError; +use crate::nyxd::NyxdClient; use async_trait::async_trait; use cosmrs::AccountId; -use mixnet_contract_common::delegation::{MixNodeDelegationResponse, OwnerProxySubKey}; -use mixnet_contract_common::families::Family; -use mixnet_contract_common::mixnode::{ +use nym_mixnet_contract_common::delegation::{MixNodeDelegationResponse, OwnerProxySubKey}; +use nym_mixnet_contract_common::families::Family; +use nym_mixnet_contract_common::mixnode::{ MixNodeDetails, MixnodeRewardingDetailsResponse, PagedMixnodesDetailsResponse, PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse, }; -use mixnet_contract_common::reward_params::{Performance, RewardingParams}; -use mixnet_contract_common::rewarding::{ +use nym_mixnet_contract_common::reward_params::{Performance, RewardingParams}; +use nym_mixnet_contract_common::rewarding::{ EstimatedCurrentEpochRewardResponse, PendingRewardResponse, }; -use mixnet_contract_common::{ +use nym_mixnet_contract_common::{ delegation, ContractBuildInformation, ContractState, ContractStateParams, - CurrentIntervalResponse, EpochEventId, GatewayBondResponse, GatewayOwnershipResponse, - IdentityKey, IntervalEventId, LayerDistribution, MixId, MixOwnershipResponse, - MixnodeDetailsResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, - PagedFamiliesResponse, PagedGatewayResponse, PagedMembersResponse, - PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse, PagedRewardedSetResponse, - PendingEpochEventsResponse, PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg, + CurrentIntervalResponse, EpochEventId, EpochStatus, GatewayBondResponse, + GatewayOwnershipResponse, IdentityKey, IntervalEventId, LayerDistribution, MixId, + MixOwnershipResponse, MixnodeDetailsResponse, NumberOfPendingEventsResponse, + PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedFamiliesResponse, + PagedGatewayResponse, PagedMembersResponse, PagedMixNodeDelegationsResponse, + PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse, + PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg, }; use serde::Deserialize; #[async_trait] pub trait MixnetQueryClient { - async fn query_mixnet_contract(&self, query: MixnetQueryMsg) -> Result + async fn query_mixnet_contract(&self, query: MixnetQueryMsg) -> Result where for<'a> T: Deserialize<'a>; // state/sys-params-related - async fn get_mixnet_contract_version(&self) -> Result { + async fn get_mixnet_contract_version(&self) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetContractVersion {}) .await } - async fn get_rewarding_validator_address(&self) -> Result { + async fn get_rewarding_validator_address(&self) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetRewardingValidatorAddress {}) .await } - async fn get_mixnet_contract_settings(&self) -> Result { + async fn get_mixnet_contract_settings(&self) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetStateParams {}) .await } - async fn get_mixnet_contract_state(&self) -> Result { + async fn get_mixnet_contract_state(&self) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetState {}) .await } - async fn get_rewarding_parameters(&self) -> Result { + async fn get_rewarding_parameters(&self) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetRewardingParams {}) .await } - async fn get_current_interval_details(&self) -> Result { + async fn get_current_epoch_status(&self) -> Result { + self.query_mixnet_contract(MixnetQueryMsg::GetEpochStatus {}) + .await + } + + async fn get_current_interval_details(&self) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetCurrentIntervalDetails {}) .await } @@ -69,7 +75,7 @@ pub trait MixnetQueryClient { &self, start_after: Option, limit: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetRewardedSet { limit, start_after }) .await } @@ -78,7 +84,7 @@ pub trait MixnetQueryClient { &self, start_after: Option, limit: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetAllFamiliesPaged { limit, start_after }) .await } @@ -87,7 +93,7 @@ pub trait MixnetQueryClient { &self, start_after: Option, limit: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetAllMembersPaged { limit, start_after }) .await } @@ -98,7 +104,7 @@ pub trait MixnetQueryClient { &self, limit: Option, start_after: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetMixNodeBonds { limit, start_after }) .await } @@ -107,7 +113,7 @@ pub trait MixnetQueryClient { &self, limit: Option, start_after: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetMixNodesDetailed { limit, start_after }) .await } @@ -116,7 +122,7 @@ pub trait MixnetQueryClient { &self, limit: Option, start_after: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodes { limit, start_after }) .await } @@ -126,7 +132,7 @@ pub trait MixnetQueryClient { owner: &AccountId, limit: Option, start_after: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByOwner { owner: owner.to_string(), limit, @@ -140,7 +146,7 @@ pub trait MixnetQueryClient { identity_key: String, limit: Option, start_after: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByIdentityKey { identity_key, limit, @@ -152,7 +158,7 @@ pub trait MixnetQueryClient { async fn get_owned_mixnode( &self, address: &AccountId, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetOwnedMixnode { address: address.to_string(), }) @@ -162,7 +168,7 @@ pub trait MixnetQueryClient { async fn get_mixnode_details( &self, mix_id: MixId, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDetails { mix_id }) .await } @@ -170,7 +176,7 @@ pub trait MixnetQueryClient { async fn get_mixnode_rewarding_details( &self, mix_id: MixId, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeRewardingDetails { mix_id }) .await } @@ -178,7 +184,7 @@ pub trait MixnetQueryClient { async fn get_mixnode_stake_saturation( &self, mix_id: MixId, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetStakeSaturation { mix_id }) .await } @@ -186,12 +192,12 @@ pub trait MixnetQueryClient { async fn get_unbonded_mixnode_information( &self, mix_id: MixId, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodeInformation { mix_id }) .await } - async fn get_layer_distribution(&self) -> Result { + async fn get_layer_distribution(&self) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetLayerDistribution {}) .await } @@ -202,7 +208,7 @@ pub trait MixnetQueryClient { &self, start_after: Option, limit: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetGateways { start_after, limit }) .await } @@ -211,7 +217,7 @@ pub trait MixnetQueryClient { async fn get_gateway_bond( &self, identity: IdentityKey, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetGatewayBond { identity }) .await } @@ -220,7 +226,7 @@ pub trait MixnetQueryClient { async fn get_owned_gateway( &self, address: &AccountId, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetOwnedGateway { address: address.to_string(), }) @@ -235,7 +241,7 @@ pub trait MixnetQueryClient { mix_id: MixId, start_after: Option, limit: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDelegations { mix_id, start_after, @@ -250,7 +256,7 @@ pub trait MixnetQueryClient { delegator: String, start_after: Option<(MixId, OwnerProxySubKey)>, limit: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetDelegatorDelegations { delegator, start_after, @@ -265,7 +271,7 @@ pub trait MixnetQueryClient { mix_id: MixId, delegator: &AccountId, proxy: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetDelegationDetails { mix_id, delegator: delegator.to_string(), @@ -279,7 +285,7 @@ pub trait MixnetQueryClient { &self, start_after: Option, limit: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetAllDelegations { start_after, limit }) .await } @@ -288,7 +294,7 @@ pub trait MixnetQueryClient { async fn get_pending_operator_reward( &self, operator: &AccountId, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetPendingOperatorReward { address: operator.to_string(), }) @@ -298,7 +304,7 @@ pub trait MixnetQueryClient { async fn get_pending_mixnode_operator_reward( &self, mix_id: MixId, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetPendingMixNodeOperatorReward { mix_id }) .await } @@ -308,7 +314,7 @@ pub trait MixnetQueryClient { delegator: &AccountId, mix_id: MixId, proxy: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetPendingDelegatorReward { address: delegator.to_string(), mix_id, @@ -322,7 +328,7 @@ pub trait MixnetQueryClient { &self, mix_id: MixId, estimated_performance: Performance, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochOperatorReward { mix_id, estimated_performance, @@ -337,7 +343,7 @@ pub trait MixnetQueryClient { mix_id: MixId, proxy: Option, estimated_performance: Performance, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochDelegatorReward { address: delegator.to_string(), mix_id, @@ -353,7 +359,7 @@ pub trait MixnetQueryClient { &self, start_after: Option, limit: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetPendingEpochEvents { start_after, limit }) .await } @@ -362,7 +368,7 @@ pub trait MixnetQueryClient { &self, start_after: Option, limit: Option, - ) -> Result { + ) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetPendingIntervalEvents { start_after, limit }) .await } @@ -370,21 +376,28 @@ pub trait MixnetQueryClient { async fn get_mixnode_details_by_identity( &self, mix_identity: IdentityKey, - ) -> Result, NymdError> { + ) -> Result, NyxdError> { self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity { mix_identity, }) .await } - async fn get_node_family_by_label(&self, label: &str) -> Result, NymdError> { + async fn get_number_of_pending_events( + &self, + ) -> Result { + self.query_mixnet_contract(MixnetQueryMsg::GetNumberOfPendingEvents {}) + .await + } + + async fn get_node_family_by_label(&self, label: &str) -> Result, NyxdError> { self.query_mixnet_contract(MixnetQueryMsg::GetFamilyByLabel { label: label.to_string(), }) .await } - async fn get_node_family_by_head(&self, head: &str) -> Result, NymdError> { + async fn get_node_family_by_head(&self, head: &str) -> Result, NyxdError> { self.query_mixnet_contract(MixnetQueryMsg::GetFamilyByHead { head: head.to_string(), }) @@ -393,11 +406,11 @@ pub trait MixnetQueryClient { } #[async_trait] -impl MixnetQueryClient for NymdClient +impl MixnetQueryClient for NyxdClient where - C: CosmWasmClient + Sync + Send, + C: CosmWasmClient + Sync + Send + Clone, { - async fn query_mixnet_contract(&self, query: MixnetQueryMsg) -> Result + async fn query_mixnet_contract(&self, query: MixnetQueryMsg) -> Result where for<'a> T: Deserialize<'a>, { @@ -410,12 +423,12 @@ where #[async_trait] impl MixnetQueryClient for crate::Client where - C: CosmWasmClient + Sync + Send, + C: CosmWasmClient + Sync + Send + Clone, { - async fn query_mixnet_contract(&self, query: MixnetQueryMsg) -> Result + async fn query_mixnet_contract(&self, query: MixnetQueryMsg) -> Result where for<'a> T: Deserialize<'a>, { - self.nymd.query_mixnet_contract(query).await + self.nyxd.query_mixnet_contract(query).await } } diff --git a/common/client-libs/validator-client/src/nymd/traits/mixnet_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/mixnet_signing_client.rs similarity index 83% rename from common/client-libs/validator-client/src/nymd/traits/mixnet_signing_client.rs rename to common/client-libs/validator-client/src/nyxd/traits/mixnet_signing_client.rs index f2a2057081..dafad09536 100644 --- a/common/client-libs/validator-client/src/nymd/traits/mixnet_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/mixnet_signing_client.rs @@ -1,16 +1,16 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::coin::Coin; -pub use crate::nymd::cosmwasm_client::client::CosmWasmClient; -use crate::nymd::cosmwasm_client::types::ExecuteResult; -use crate::nymd::error::NymdError; -use crate::nymd::{Fee, NymdClient, SigningCosmWasmClient}; +use crate::nyxd::coin::Coin; +pub use crate::nyxd::cosmwasm_client::client::CosmWasmClient; +use crate::nyxd::cosmwasm_client::types::ExecuteResult; +use crate::nyxd::error::NyxdError; +use crate::nyxd::{Fee, NyxdClient, SigningCosmWasmClient}; use async_trait::async_trait; use cosmrs::AccountId; -use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; -use mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance}; -use mixnet_contract_common::{ +use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; +use nym_mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance}; +use nym_mixnet_contract_common::{ ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, LayerAssignment, MixId, MixNode, }; @@ -21,7 +21,7 @@ pub trait MixnetSigningClient { fee: Option, msg: MixnetExecuteMsg, funds: Vec, - ) -> Result; + ) -> Result; // state/sys-params-related @@ -29,7 +29,7 @@ pub trait MixnetSigningClient { &self, address: AccountId, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UpdateRewardingValidatorAddress { @@ -44,7 +44,7 @@ pub trait MixnetSigningClient { &self, updated_parameters: ContractStateParams, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UpdateContractStateParams { updated_parameters }, @@ -58,7 +58,7 @@ pub trait MixnetSigningClient { active_set_size: u32, force_immediately: bool, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UpdateActiveSetSize { @@ -75,7 +75,7 @@ pub trait MixnetSigningClient { updated_params: IntervalRewardingParamsUpdate, force_immediately: bool, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UpdateRewardingParams { @@ -93,7 +93,7 @@ pub trait MixnetSigningClient { epoch_duration_secs: u64, force_immediately: bool, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UpdateIntervalConfig { @@ -106,12 +106,17 @@ pub trait MixnetSigningClient { .await } + async fn begin_epoch_transition(&self, fee: Option) -> Result { + self.execute_mixnet_contract(fee, MixnetExecuteMsg::BeginEpochTransition {}, vec![]) + .await + } + async fn advance_current_epoch( &self, new_rewarded_set: Vec, expected_active_set_size: u32, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::AdvanceCurrentEpoch { @@ -127,7 +132,7 @@ pub trait MixnetSigningClient { &self, limit: Option, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::ReconcileEpochEvents { limit }, @@ -142,7 +147,7 @@ pub trait MixnetSigningClient { owner_signature: String, label: String, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::CreateFamily { @@ -160,7 +165,7 @@ pub trait MixnetSigningClient { owner_signature: String, label: String, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::CreateFamilyOnBehalf { @@ -178,7 +183,7 @@ pub trait MixnetSigningClient { signature: String, family_head: String, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::JoinFamily { @@ -193,15 +198,17 @@ pub trait MixnetSigningClient { async fn join_family_on_behalf( &self, member_address: String, - signature: String, + node_identity_signature: String, + family_signature: String, family_head: String, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::JoinFamilyOnBehalf { member_address, - signature, + node_identity_signature, + family_signature, family_head, }, vec![], @@ -214,7 +221,7 @@ pub trait MixnetSigningClient { signature: String, family_head: String, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::LeaveFamily { @@ -229,15 +236,15 @@ pub trait MixnetSigningClient { async fn leave_family_on_behalf( &self, member_address: String, - signature: String, + node_identity_signature: String, family_head: String, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::LeaveFamilyOnBehalf { member_address, - signature, + node_identity_signature, family_head, }, vec![], @@ -250,7 +257,7 @@ pub trait MixnetSigningClient { signature: String, member: String, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::KickFamilyMember { signature, member }, @@ -265,7 +272,7 @@ pub trait MixnetSigningClient { signature: String, member: String, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::KickFamilyMemberOnBehalf { @@ -286,7 +293,7 @@ pub trait MixnetSigningClient { owner_signature: String, pledge: Coin, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::BondMixnode { @@ -307,7 +314,7 @@ pub trait MixnetSigningClient { owner_signature: String, pledge: Coin, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::BondMixnodeOnBehalf { @@ -325,7 +332,7 @@ pub trait MixnetSigningClient { &self, additional_pledge: Coin, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::PledgeMore {}, @@ -339,7 +346,7 @@ pub trait MixnetSigningClient { owner: AccountId, additional_pledge: Coin, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::PledgeMoreOnBehalf { @@ -350,7 +357,7 @@ pub trait MixnetSigningClient { .await } - async fn unbond_mixnode(&self, fee: Option) -> Result { + async fn unbond_mixnode(&self, fee: Option) -> Result { self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![]) .await } @@ -359,7 +366,7 @@ pub trait MixnetSigningClient { &self, owner: AccountId, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UnbondMixnodeOnBehalf { @@ -374,7 +381,7 @@ pub trait MixnetSigningClient { &self, new_costs: MixNodeCostParams, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UpdateMixnodeCostParams { new_costs }, @@ -388,7 +395,7 @@ pub trait MixnetSigningClient { owner: AccountId, new_costs: MixNodeCostParams, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UpdateMixnodeCostParamsOnBehalf { @@ -404,7 +411,7 @@ pub trait MixnetSigningClient { &self, new_config: MixNodeConfigUpdate, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UpdateMixnodeConfig { new_config }, @@ -418,7 +425,7 @@ pub trait MixnetSigningClient { owner: AccountId, new_config: MixNodeConfigUpdate, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UpdateMixnodeConfigOnBehalf { @@ -438,7 +445,7 @@ pub trait MixnetSigningClient { owner_signature: String, pledge: Coin, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::BondGateway { @@ -457,7 +464,7 @@ pub trait MixnetSigningClient { owner_signature: String, pledge: Coin, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::BondGatewayOnBehalf { @@ -470,7 +477,7 @@ pub trait MixnetSigningClient { .await } - async fn unbond_gateway(&self, fee: Option) -> Result { + async fn unbond_gateway(&self, fee: Option) -> Result { self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondGateway {}, vec![]) .await } @@ -479,7 +486,7 @@ pub trait MixnetSigningClient { &self, owner: AccountId, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UnbondGatewayOnBehalf { @@ -497,7 +504,7 @@ pub trait MixnetSigningClient { mix_id: MixId, amount: Coin, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::DelegateToMixnode { mix_id }, @@ -512,7 +519,7 @@ pub trait MixnetSigningClient { mix_id: MixId, amount: Coin, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::DelegateToMixnodeOnBehalf { @@ -528,7 +535,7 @@ pub trait MixnetSigningClient { &self, mix_id: MixId, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UndelegateFromMixnode { mix_id }, @@ -542,7 +549,7 @@ pub trait MixnetSigningClient { delegate: AccountId, mix_id: MixId, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::UndelegateFromMixnodeOnBehalf { @@ -561,7 +568,7 @@ pub trait MixnetSigningClient { mix_id: MixId, performance: Performance, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::RewardMixnode { @@ -573,7 +580,7 @@ pub trait MixnetSigningClient { .await } - async fn withdraw_operator_reward(&self, fee: Option) -> Result { + async fn withdraw_operator_reward(&self, fee: Option) -> Result { self.execute_mixnet_contract(fee, MixnetExecuteMsg::WithdrawOperatorReward {}, vec![]) .await } @@ -582,7 +589,7 @@ pub trait MixnetSigningClient { &self, owner: AccountId, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::WithdrawOperatorRewardOnBehalf { @@ -597,7 +604,7 @@ pub trait MixnetSigningClient { &self, mix_id: MixId, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::WithdrawDelegatorReward { mix_id }, @@ -611,7 +618,7 @@ pub trait MixnetSigningClient { owner: AccountId, mix_id: MixId, fee: Option, - ) -> Result { + ) -> Result { self.execute_mixnet_contract( fee, MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf { @@ -625,16 +632,16 @@ pub trait MixnetSigningClient { } #[async_trait] -impl MixnetSigningClient for NymdClient +impl MixnetSigningClient for NyxdClient where - C: SigningCosmWasmClient + Sync + Send, + C: SigningCosmWasmClient + Sync + Send + Clone, { async fn execute_mixnet_contract( &self, fee: Option, msg: MixnetExecuteMsg, funds: Vec, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let memo = msg.default_memo(); self.client @@ -653,14 +660,14 @@ where #[async_trait] impl MixnetSigningClient for crate::Client where - C: SigningCosmWasmClient + Sync + Send, + C: SigningCosmWasmClient + Sync + Send + Clone, { async fn execute_mixnet_contract( &self, fee: Option, msg: MixnetExecuteMsg, funds: Vec, - ) -> Result { - self.nymd.execute_mixnet_contract(fee, msg, funds).await + ) -> Result { + self.nyxd.execute_mixnet_contract(fee, msg, funds).await } } diff --git a/common/client-libs/validator-client/src/nymd/traits/mod.rs b/common/client-libs/validator-client/src/nyxd/traits/mod.rs similarity index 93% rename from common/client-libs/validator-client/src/nymd/traits/mod.rs rename to common/client-libs/validator-client/src/nyxd/traits/mod.rs index 11e70cd484..90e75adecd 100644 --- a/common/client-libs/validator-client/src/nymd/traits/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/mod.rs @@ -5,6 +5,7 @@ mod coconut_bandwidth_query_client; mod coconut_bandwidth_signing_client; mod dkg_query_client; mod dkg_signing_client; +mod group_query_client; mod mixnet_query_client; mod mixnet_signing_client; mod multisig_query_client; @@ -16,6 +17,7 @@ pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient; pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient; pub use dkg_query_client::DkgQueryClient; pub use dkg_signing_client::DkgSigningClient; +pub use group_query_client::GroupQueryClient; pub use mixnet_query_client::MixnetQueryClient; pub use mixnet_signing_client::MixnetSigningClient; pub use multisig_query_client::MultisigQueryClient; diff --git a/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/multisig_query_client.rs similarity index 72% rename from common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs rename to common/client-libs/validator-client/src/nyxd/traits/multisig_query_client.rs index 89643e8136..392a9a9cd0 100644 --- a/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/multisig_query_client.rs @@ -1,27 +1,27 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::error::NymdError; -use crate::nymd::{CosmWasmClient, NymdClient}; +use crate::nyxd::error::NyxdError; +use crate::nyxd::{CosmWasmClient, NyxdClient}; use cw3::{ProposalListResponse, ProposalResponse}; -use multisig_contract_common::msg::QueryMsg; +use nym_multisig_contract_common::msg::QueryMsg; use async_trait::async_trait; #[async_trait] pub trait MultisigQueryClient { - async fn get_proposal(&self, proposal_id: u64) -> Result; + async fn get_proposal(&self, proposal_id: u64) -> Result; async fn list_proposals( &self, start_after: Option, limit: Option, - ) -> Result; + ) -> Result; } #[async_trait] -impl MultisigQueryClient for NymdClient { - async fn get_proposal(&self, proposal_id: u64) -> Result { +impl MultisigQueryClient for NyxdClient { + async fn get_proposal(&self, proposal_id: u64) -> Result { let request = QueryMsg::Proposal { proposal_id }; self.client .query_contract_smart(self.multisig_contract_address(), &request) @@ -32,7 +32,7 @@ impl MultisigQueryClient for NymdClient { &self, start_after: Option, limit: Option, - ) -> Result { + ) -> Result { let request = QueryMsg::ListProposals { start_after, limit }; self.client .query_contract_smart(self.multisig_contract_address(), &request) diff --git a/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/multisig_signing_client.rs similarity index 81% rename from common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs rename to common/client-libs/validator-client/src/nyxd/traits/multisig_signing_client.rs index a715c85075..6d716ece8a 100644 --- a/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/multisig_signing_client.rs @@ -1,14 +1,14 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; -use crate::nymd::cosmwasm_client::types::ExecuteResult; -use crate::nymd::error::NymdError; -use crate::nymd::{Fee, NymdClient}; +pub use crate::nyxd::cosmwasm_client::signing_client::SigningCosmWasmClient; +use crate::nyxd::cosmwasm_client::types::ExecuteResult; +use crate::nyxd::error::NyxdError; +use crate::nyxd::{Fee, NyxdClient}; -use coconut_bandwidth_contract_common::msg::ExecuteMsg as CoconutBandwidthExecuteMsg; use cw3::Vote; -use multisig_contract_common::msg::ExecuteMsg; +use nym_coconut_bandwidth_contract_common::msg::ExecuteMsg as CoconutBandwidthExecuteMsg; +use nym_multisig_contract_common::msg::ExecuteMsg; use async_trait::async_trait; use cosmwasm_std::{to_binary, Coin, CosmosMsg, WasmMsg}; @@ -21,31 +21,31 @@ pub trait MultisigSigningClient { blinded_serial_number: String, voucher_value: u128, fee: Option, - ) -> Result; + ) -> Result; async fn vote_proposal( &self, proposal_id: u64, yes: bool, fee: Option, - ) -> Result; + ) -> Result; async fn execute_proposal( &self, proposal_id: u64, fee: Option, - ) -> Result; + ) -> Result; } #[async_trait] -impl MultisigSigningClient for NymdClient { +impl MultisigSigningClient for NyxdClient { async fn propose_release_funds( &self, title: String, blinded_serial_number: String, voucher_value: u128, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let release_funds_req = CoconutBandwidthExecuteMsg::ReleaseFunds { funds: Coin::new( @@ -81,7 +81,7 @@ impl MultisigSigningClient for NymdClien proposal_id: u64, vote_yes: bool, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let vote = if vote_yes { Vote::Yes } else { Vote::No }; let req = ExecuteMsg::Vote { proposal_id, vote }; @@ -101,7 +101,7 @@ impl MultisigSigningClient for NymdClien &self, proposal_id: u64, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = ExecuteMsg::Execute { proposal_id }; self.client diff --git a/common/client-libs/validator-client/src/nyxd/traits/vesting_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/vesting_query_client.rs new file mode 100644 index 0000000000..64774af6e6 --- /dev/null +++ b/common/client-libs/validator-client/src/nyxd/traits/vesting_query_client.rs @@ -0,0 +1,321 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::nyxd::coin::Coin; +pub use crate::nyxd::cosmwasm_client::client::CosmWasmClient; +use crate::nyxd::error::NyxdError; +use crate::nyxd::NyxdClient; +use async_trait::async_trait; +use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp}; +use nym_contracts_common::ContractBuildInformation; +use nym_mixnet_contract_common::MixId; +use nym_vesting_contract_common::{ + messages::QueryMsg as VestingQueryMsg, AccountVestingCoins, AccountsResponse, + AllDelegationsResponse, BaseVestingAccountInfo, DelegationTimesResponse, + OriginalVestingResponse, Period, PledgeData, VestingCoinsResponse, VestingDelegation, +}; +use serde::Deserialize; +use vesting_contract::vesting::Account; + +#[async_trait] +pub trait VestingQueryClient { + async fn query_vesting_contract(&self, query: VestingQueryMsg) -> Result + where + for<'a> T: Deserialize<'a>; + + async fn get_vesting_contract_version(&self) -> Result { + self.query_vesting_contract(VestingQueryMsg::GetContractVersion {}) + .await + } + + async fn get_all_accounts_paged( + &self, + start_next_after: Option, + limit: Option, + ) -> Result { + self.query_vesting_contract(VestingQueryMsg::GetAccountsPaged { + start_next_after, + limit, + }) + .await + } + + async fn get_all_accounts_vesting_coins_paged( + &self, + start_next_after: Option, + limit: Option, + ) -> Result { + self.query_vesting_contract(VestingQueryMsg::GetAccountsVestingCoinsPaged { + start_next_after, + limit, + }) + .await + } + + async fn locked_coins( + &self, + vesting_account_address: &str, + block_time: Option, + ) -> Result { + self.query_vesting_contract::(VestingQueryMsg::LockedCoins { + vesting_account_address: vesting_account_address.to_string(), + block_time, + }) + .await + .map(Into::into) + } + + async fn spendable_coins( + &self, + vesting_account_address: &str, + block_time: Option, + ) -> Result { + self.query_vesting_contract::(VestingQueryMsg::SpendableCoins { + vesting_account_address: vesting_account_address.to_string(), + block_time, + }) + .await + .map(Into::into) + } + + async fn vested_coins( + &self, + vesting_account_address: &str, + block_time: Option, + ) -> Result { + self.query_vesting_contract::(VestingQueryMsg::GetVestedCoins { + vesting_account_address: vesting_account_address.to_string(), + block_time, + }) + .await + .map(Into::into) + } + + async fn vesting_coins( + &self, + vesting_account_address: &str, + block_time: Option, + ) -> Result { + self.query_vesting_contract::(VestingQueryMsg::GetVestingCoins { + vesting_account_address: vesting_account_address.to_string(), + block_time, + }) + .await + .map(Into::into) + } + + async fn vesting_start_time( + &self, + vesting_account_address: &str, + ) -> Result { + self.query_vesting_contract(VestingQueryMsg::GetStartTime { + vesting_account_address: vesting_account_address.to_string(), + }) + .await + } + + async fn vesting_end_time( + &self, + vesting_account_address: &str, + ) -> Result { + self.query_vesting_contract(VestingQueryMsg::GetEndTime { + vesting_account_address: vesting_account_address.to_string(), + }) + .await + } + + async fn original_vesting( + &self, + vesting_account_address: &str, + ) -> Result { + self.query_vesting_contract(VestingQueryMsg::GetOriginalVesting { + vesting_account_address: vesting_account_address.to_string(), + }) + .await + } + + async fn get_historical_vesting_staking_reward( + &self, + vesting_account_address: &str, + ) -> Result { + self.query_vesting_contract::( + VestingQueryMsg::GetHistoricalVestingStakingReward { + vesting_account_address: vesting_account_address.to_string(), + }, + ) + .await + .map(Into::into) + } + + async fn get_spendable_vested_coins( + &self, + vesting_account_address: &str, + ) -> Result { + self.query_vesting_contract::(VestingQueryMsg::GetSpendableVestedCoins { + vesting_account_address: vesting_account_address.to_string(), + }) + .await + .map(Into::into) + } + + async fn get_spendable_reward_coins( + &self, + vesting_account_address: &str, + ) -> Result { + self.query_vesting_contract::(VestingQueryMsg::GetSpendableRewardCoins { + vesting_account_address: vesting_account_address.to_string(), + }) + .await + .map(Into::into) + } + + async fn get_delegated_coins(&self, vesting_account_address: &str) -> Result { + self.query_vesting_contract::(VestingQueryMsg::GetDelegatedCoins { + vesting_account_address: vesting_account_address.to_string(), + }) + .await + .map(Into::into) + } + + async fn get_pledged_coins(&self, vesting_account_address: &str) -> Result { + self.query_vesting_contract::(VestingQueryMsg::GetPledgedCoins { + vesting_account_address: vesting_account_address.to_string(), + }) + .await + .map(Into::into) + } + + async fn get_staked_coins(&self, vesting_account_address: &str) -> Result { + self.query_vesting_contract::(VestingQueryMsg::GetStakedCoins { + vesting_account_address: vesting_account_address.to_string(), + }) + .await + .map(Into::into) + } + + async fn get_withdrawn_coins(&self, vesting_account_address: &str) -> Result { + self.query_vesting_contract::(VestingQueryMsg::GetWithdrawnCoins { + vesting_account_address: vesting_account_address.to_string(), + }) + .await + .map(Into::into) + } + + async fn get_account(&self, address: &str) -> Result { + self.query_vesting_contract(VestingQueryMsg::GetAccount { + address: address.to_string(), + }) + .await + } + + async fn get_mixnode_pledge(&self, address: &str) -> Result, NyxdError> { + self.query_vesting_contract(VestingQueryMsg::GetMixnode { + address: address.to_string(), + }) + .await + } + + async fn get_gateway_pledge(&self, address: &str) -> Result, NyxdError> { + self.query_vesting_contract(VestingQueryMsg::GetGateway { + address: address.to_string(), + }) + .await + } + + async fn get_current_vesting_period(&self, address: &str) -> Result { + self.query_vesting_contract(VestingQueryMsg::GetCurrentVestingPeriod { + address: address.to_string(), + }) + .await + } + + async fn get_delegation_timestamps( + &self, + address: &str, + mix_id: MixId, + ) -> Result { + self.query_vesting_contract(VestingQueryMsg::GetDelegationTimes { + address: address.to_string(), + mix_id, + }) + .await + } + + async fn get_all_vesting_delegations_paged( + &self, + start_after: Option<(u32, MixId, u64)>, + limit: Option, + ) -> Result { + self.query_vesting_contract(VestingQueryMsg::GetAllDelegations { start_after, limit }) + .await + } + + async fn get_all_vesting_delegations(&self) -> Result, NyxdError> { + let mut delegations = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .get_all_vesting_delegations_paged(start_after.take(), None) + .await?; + delegations.append(&mut paged_response.delegations); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(delegations) + } + + async fn get_all_accounts_info(&self) -> Result, NyxdError> { + let mut accounts = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .get_all_accounts_paged(start_after.take(), None) + .await?; + accounts.append(&mut paged_response.accounts); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res.into_string()) + } else { + break; + } + } + + Ok(accounts) + } + + async fn get_all_accounts_vesting_coins(&self) -> Result, NyxdError> { + let mut accounts = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .get_all_accounts_vesting_coins_paged(start_after.take(), None) + .await?; + accounts.append(&mut paged_response.accounts); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res.into_string()) + } else { + break; + } + } + + Ok(accounts) + } +} + +#[async_trait] +impl VestingQueryClient for NyxdClient { + async fn query_vesting_contract(&self, query: VestingQueryMsg) -> Result + where + for<'a> T: Deserialize<'a>, + { + self.client + .query_contract_smart(self.vesting_contract_address(), &query) + .await + } +} diff --git a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/vesting_signing_client.rs similarity index 82% rename from common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs rename to common/client-libs/validator-client/src/nyxd/traits/vesting_signing_client.rs index 120e9ca2e0..e2d622d12d 100644 --- a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/vesting_signing_client.rs @@ -1,15 +1,17 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; -use crate::nymd::cosmwasm_client::types::ExecuteResult; -use crate::nymd::error::NymdError; -use crate::nymd::{Coin, Fee, NymdClient}; +pub use crate::nyxd::cosmwasm_client::signing_client::SigningCosmWasmClient; +use crate::nyxd::cosmwasm_client::types::ExecuteResult; +use crate::nyxd::error::NyxdError; +use crate::nyxd::{Coin, Fee, NyxdClient}; use async_trait::async_trait; -use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; -use mixnet_contract_common::{Gateway, MixId, MixNode}; -use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification}; -use vesting_contract_common::PledgeCap; +use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; +use nym_mixnet_contract_common::{Gateway, MixId, MixNode}; +use nym_vesting_contract_common::messages::{ + ExecuteMsg as VestingExecuteMsg, VestingSpecification, +}; +use nym_vesting_contract_common::PledgeCap; #[async_trait] pub trait VestingSigningClient { @@ -18,25 +20,25 @@ pub trait VestingSigningClient { fee: Option, msg: VestingExecuteMsg, funds: Vec, - ) -> Result; + ) -> Result; async fn vesting_update_mixnode_cost_params( &self, new_costs: MixNodeCostParams, fee: Option, - ) -> Result; + ) -> Result; async fn vesting_update_mixnode_config( &self, new_config: MixNodeConfigUpdate, fee: Option, - ) -> Result; + ) -> Result; async fn update_mixnet_address( &self, address: &str, fee: Option, - ) -> Result; + ) -> Result; async fn vesting_bond_gateway( &self, @@ -44,16 +46,16 @@ pub trait VestingSigningClient { owner_signature: &str, pledge: Coin, fee: Option, - ) -> Result; + ) -> Result; - async fn vesting_unbond_gateway(&self, fee: Option) -> Result; + async fn vesting_unbond_gateway(&self, fee: Option) -> Result; async fn vesting_track_unbond_gateway( &self, owner: &str, amount: Coin, fee: Option, - ) -> Result; + ) -> Result; async fn vesting_bond_mixnode( &self, @@ -62,13 +64,13 @@ pub trait VestingSigningClient { owner_signature: &str, pledge: Coin, fee: Option, - ) -> Result; + ) -> Result; async fn vesting_pledge_more( &self, additional_pledge: Coin, fee: Option, - ) -> Result { + ) -> Result { self.execute_vesting_contract( fee, VestingExecuteMsg::PledgeMore { @@ -79,20 +81,20 @@ pub trait VestingSigningClient { .await } - async fn vesting_unbond_mixnode(&self, fee: Option) -> Result; + async fn vesting_unbond_mixnode(&self, fee: Option) -> Result; async fn vesting_track_unbond_mixnode( &self, owner: &str, amount: Coin, fee: Option, - ) -> Result; + ) -> Result; async fn withdraw_vested_coins( &self, amount: Coin, fee: Option, - ) -> Result; + ) -> Result; async fn vesting_track_undelegation( &self, @@ -100,20 +102,22 @@ pub trait VestingSigningClient { mix_id: MixId, amount: Coin, fee: Option, - ) -> Result; + ) -> Result; async fn vesting_delegate_to_mixnode( &self, mix_id: MixId, amount: Coin, + on_behalf_of: Option, fee: Option, - ) -> Result; + ) -> Result; async fn vesting_undelegate_from_mixnode( &self, mix_id: MixId, + on_behalf_of: Option, fee: Option, - ) -> Result; + ) -> Result; async fn create_periodic_vesting_account( &self, @@ -123,17 +127,17 @@ pub trait VestingSigningClient { amount: Coin, cap: Option, fee: Option, - ) -> Result; + ) -> Result; } #[async_trait] -impl VestingSigningClient for NymdClient { +impl VestingSigningClient for NyxdClient { async fn execute_vesting_contract( &self, fee: Option, msg: VestingExecuteMsg, funds: Vec, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let memo = msg.name().to_string(); self.client @@ -152,7 +156,7 @@ impl VestingSigningClient for NymdClient &self, new_costs: MixNodeCostParams, fee: Option, - ) -> Result { + ) -> Result { self.execute_vesting_contract( fee, VestingExecuteMsg::UpdateMixnodeCostParams { new_costs }, @@ -165,7 +169,7 @@ impl VestingSigningClient for NymdClient &self, new_config: MixNodeConfigUpdate, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::UpdateMixnodeConfig { new_config }; self.client @@ -184,7 +188,7 @@ impl VestingSigningClient for NymdClient &self, address: &str, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::UpdateMixnetAddress { address: address.to_string(), @@ -207,7 +211,7 @@ impl VestingSigningClient for NymdClient owner_signature: &str, pledge: Coin, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::BondGateway { gateway, @@ -226,7 +230,7 @@ impl VestingSigningClient for NymdClient .await } - async fn vesting_unbond_gateway(&self, fee: Option) -> Result { + async fn vesting_unbond_gateway(&self, fee: Option) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::UnbondGateway {}; self.client @@ -246,7 +250,7 @@ impl VestingSigningClient for NymdClient owner: &str, amount: Coin, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::TrackUnbondGateway { owner: owner.to_string(), @@ -271,7 +275,7 @@ impl VestingSigningClient for NymdClient owner_signature: &str, pledge: Coin, fee: Option, - ) -> Result { + ) -> Result { self.execute_vesting_contract( fee, VestingExecuteMsg::BondMixnode { @@ -285,7 +289,7 @@ impl VestingSigningClient for NymdClient .await } - async fn vesting_unbond_mixnode(&self, fee: Option) -> Result { + async fn vesting_unbond_mixnode(&self, fee: Option) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::UnbondMixnode {}; self.client @@ -305,7 +309,7 @@ impl VestingSigningClient for NymdClient owner: &str, amount: Coin, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::TrackUnbondMixnode { owner: owner.to_string(), @@ -327,7 +331,7 @@ impl VestingSigningClient for NymdClient &self, amount: Coin, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::WithdrawVestedCoins { amount: amount.into(), @@ -350,7 +354,7 @@ impl VestingSigningClient for NymdClient mix_id: MixId, amount: Coin, fee: Option, - ) -> Result { + ) -> Result { self.execute_vesting_contract( fee, VestingExecuteMsg::TrackUndelegation { @@ -367,13 +371,15 @@ impl VestingSigningClient for NymdClient &self, mix_id: MixId, amount: Coin, + on_behalf_of: Option, fee: Option, - ) -> Result { + ) -> Result { self.execute_vesting_contract( fee, VestingExecuteMsg::DelegateToMixnode { mix_id, amount: amount.into(), + on_behalf_of, }, vec![], ) @@ -383,11 +389,15 @@ impl VestingSigningClient for NymdClient async fn vesting_undelegate_from_mixnode( &self, mix_id: MixId, + on_behalf_of: Option, fee: Option, - ) -> Result { + ) -> Result { self.execute_vesting_contract( fee, - VestingExecuteMsg::UndelegateFromMixnode { mix_id }, + VestingExecuteMsg::UndelegateFromMixnode { + mix_id, + on_behalf_of, + }, vec![], ) .await @@ -401,7 +411,7 @@ impl VestingSigningClient for NymdClient amount: Coin, cap: Option, fee: Option, - ) -> Result { + ) -> Result { let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let req = VestingExecuteMsg::CreateAccount { owner_address: owner_address.to_string(), diff --git a/common/client-libs/validator-client/src/nymd/wallet/mod.rs b/common/client-libs/validator-client/src/nyxd/wallet/mod.rs similarity index 78% rename from common/client-libs/validator-client/src/nymd/wallet/mod.rs rename to common/client-libs/validator-client/src/nyxd/wallet/mod.rs index c78aacde70..c859b6fecc 100644 --- a/common/client-libs/validator-client/src/nymd/wallet/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/wallet/mod.rs @@ -1,13 +1,14 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::error::NymdError; -use config::defaults; +use crate::nyxd::error::NyxdError; use cosmrs::bip32::{DerivationPath, XPrv}; use cosmrs::crypto::secp256k1::{Signature, SigningKey}; use cosmrs::crypto::PublicKey; use cosmrs::tx::SignDoc; use cosmrs::{tx, AccountId}; +use nym_config::defaults; +use zeroize::Zeroize; /// Derivation information required to derive a keypair and an address from a mnemonic. #[derive(Debug, Clone)] @@ -56,22 +57,43 @@ pub struct DirectSecp256k1HdWallet { accounts: Vec, } +impl Zeroize for DirectSecp256k1HdWallet { + fn zeroize(&mut self) { + // in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it) + // and the memory would have been filled with zeroes. + // + // we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing + // of overwriting it with a fresh mnemonic that was never used before + // + // note: this function can only fail on an invalid word count, which clearly is not the case here + self.secret = bip39::Mnemonic::generate(self.secret.word_count()).unwrap(); + self.seed.zeroize(); + // there's nothing secret about derivation paths + } +} + +impl Drop for DirectSecp256k1HdWallet { + fn drop(&mut self) { + self.zeroize() + } +} + impl DirectSecp256k1HdWallet { pub fn builder(prefix: &str) -> DirectSecp256k1HdWalletBuilder { DirectSecp256k1HdWalletBuilder::new(prefix) } /// Restores a wallet from the given BIP39 mnemonic using default options. - pub fn from_mnemonic(prefix: &str, mnemonic: bip39::Mnemonic) -> Result { + pub fn from_mnemonic(prefix: &str, mnemonic: bip39::Mnemonic) -> Self { DirectSecp256k1HdWalletBuilder::new(prefix).build(mnemonic) } - pub fn generate(prefix: &str, word_count: usize) -> Result { + pub fn generate(prefix: &str, word_count: usize) -> Result { let mneomonic = bip39::Mnemonic::generate(word_count)?; - Self::from_mnemonic(prefix, mneomonic) + Ok(Self::from_mnemonic(prefix, mneomonic)) } - fn derive_keypair(&self, hd_path: &DerivationPath) -> Result { + fn derive_keypair(&self, hd_path: &DerivationPath) -> Result { let extended_private_key = XPrv::derive_from_path(self.seed, hd_path)?; let private_key: SigningKey = extended_private_key.into(); @@ -80,7 +102,7 @@ impl DirectSecp256k1HdWallet { Ok((private_key, public_key)) } - pub fn try_derive_accounts(&self) -> Result, NymdError> { + pub fn try_derive_accounts(&self) -> Result, NyxdError> { let mut accounts = Vec::with_capacity(self.accounts.len()); for derivation_info in &self.accounts { let keypair = self.derive_keypair(&derivation_info.hd_path)?; @@ -89,7 +111,7 @@ impl DirectSecp256k1HdWallet { let address = keypair .1 .account_id(&derivation_info.prefix) - .map_err(|_| NymdError::AccountDerivationError)?; + .map_err(|_| NyxdError::AccountDerivationError)?; accounts.push(AccountData { address, @@ -109,47 +131,50 @@ impl DirectSecp256k1HdWallet { &self, signer: &AccountData, message: &[u8], - ) -> Result { + ) -> Result { signer .private_key .sign(message) - .map_err(|_| NymdError::SigningFailure) + .map_err(|_| NyxdError::SigningFailure) } pub fn sign_direct_with_account( &self, signer: &AccountData, sign_doc: SignDoc, - ) -> Result { - // ideally I'd prefer to have the entire error put into the NymdError::SigningFailure + ) -> Result { + // ideally I'd prefer to have the entire error put into the NyxdError::SigningFailure // but I'm super hesitant to trying to downcast the eyre::Report to cosmrs::error::Error sign_doc .sign(&signer.private_key) - .map_err(|_| NymdError::SigningFailure) + .map_err(|_| NyxdError::SigningFailure) } pub fn sign_direct( &self, signer_address: &AccountId, sign_doc: SignDoc, - ) -> Result { + ) -> Result { // I hate deriving accounts at every sign here so much : ( let accounts = self.try_derive_accounts()?; let account = accounts .iter() .find(|account| &account.address == signer_address) - .ok_or_else(|| NymdError::SigningAccountNotFound(signer_address.clone()))?; + .ok_or_else(|| NyxdError::SigningAccountNotFound(signer_address.clone()))?; self.sign_direct_with_account(account, sign_doc) } } #[must_use] +#[derive(Zeroize)] +#[zeroize(drop)] pub struct DirectSecp256k1HdWalletBuilder { /// The password to use when deriving a BIP39 seed from a mnemonic. bip39_password: String, /// The BIP-32/SLIP-10 derivation paths + #[zeroize(skip)] hd_paths: Vec, /// The bech32 address prefix (human readable part) @@ -185,29 +210,29 @@ impl DirectSecp256k1HdWalletBuilder { self } - pub fn build(self, mnemonic: bip39::Mnemonic) -> Result { + pub fn build(self, mnemonic: bip39::Mnemonic) -> DirectSecp256k1HdWallet { let seed = mnemonic.to_seed(&self.bip39_password); - let prefix = self.prefix; + let prefix = self.prefix.clone(); let accounts = self .hd_paths - .into_iter() + .iter() .map(|hd_path| Secp256k1Derivation { - hd_path, + hd_path: hd_path.clone(), prefix: prefix.clone(), }) .collect(); - Ok(DirectSecp256k1HdWallet { + DirectSecp256k1HdWallet { accounts, seed, secret: mnemonic, - }) + } } } #[cfg(test)] mod tests { - use network_defaults::NymNetworkDetails; + use nym_network_defaults::NymNetworkDetails; use super::*; @@ -229,8 +254,7 @@ mod tests { "n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf", ]; for (idx, mnemonic) in mnemonics.iter().enumerate() { - let wallet = - DirectSecp256k1HdWallet::from_mnemonic(&prefix, mnemonic.parse().unwrap()).unwrap(); + let wallet = DirectSecp256k1HdWallet::from_mnemonic(&prefix, mnemonic.parse().unwrap()); assert_eq!( wallet.try_derive_accounts().unwrap()[0].address, addrs[idx].parse().unwrap() diff --git a/common/coconut-interface/Cargo.toml b/common/coconut-interface/Cargo.toml index 285750cb3a..18f9115052 100644 --- a/common/coconut-interface/Cargo.toml +++ b/common/coconut-interface/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "coconut-interface" +name = "nym-coconut-interface" version = "0.1.0" edition = "2021" description = "Crutch library until there is proper SerDe support for coconut structs" diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 7c4cd72213..e8ea53daed 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -18,6 +18,8 @@ pub struct Credential { theta: Theta, voucher_value: u64, voucher_info: String, + #[getset(get = "pub")] + epoch_id: u64, } impl Credential { pub fn new( @@ -25,12 +27,14 @@ impl Credential { theta: Theta, voucher_value: u64, voucher_info: String, + epoch_id: u64, ) -> Credential { Credential { n_params, theta, voucher_value, voucher_info, + epoch_id, } } @@ -68,6 +72,7 @@ impl Credential { let theta_bytes = self.theta.to_bytes(); let theta_bytes_len = theta_bytes.len(); let voucher_value_bytes = self.voucher_value.to_be_bytes(); + let epoch_id_bytes = self.epoch_id.to_be_bytes(); let voucher_info_bytes = self.voucher_info.as_bytes(); let voucher_info_len = voucher_info_bytes.len(); @@ -76,6 +81,7 @@ impl Credential { bytes.extend_from_slice(&(theta_bytes_len as u64).to_be_bytes()); bytes.extend_from_slice(&theta_bytes); bytes.extend_from_slice(&voucher_value_bytes); + bytes.extend_from_slice(&epoch_id_bytes); bytes.extend_from_slice(voucher_info_bytes); bytes @@ -103,7 +109,9 @@ impl Credential { .map_err(|e| CoconutError::Deserialization(e.to_string()))?; eight_byte.copy_from_slice(&bytes[12 + theta_len as usize..20 + theta_len as usize]); let voucher_value = u64::from_be_bytes(eight_byte); - let voucher_info = String::from_utf8(bytes[20 + theta_len as usize..].to_vec()) + eight_byte.copy_from_slice(&bytes[20 + theta_len as usize..28 + theta_len as usize]); + let epoch_id = u64::from_be_bytes(eight_byte); + let voucher_info = String::from_utf8(bytes[28 + theta_len as usize..].to_vec()) .map_err(|e| CoconutError::Deserialization(e.to_string()))?; Ok(Credential { @@ -111,6 +119,7 @@ impl Credential { theta, voucher_value, voucher_info, + epoch_id, }) } } @@ -166,7 +175,7 @@ mod tests { binding_number, ) .unwrap(); - let credential = Credential::new(4, theta, voucher_value, voucher_info); + let credential = Credential::new(4, theta, voucher_value, voucher_info, 42); let serialized_credential = credential.as_bytes(); let deserialized_credential = Credential::from_bytes(&serialized_credential).unwrap(); diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index ce0195cce4..01d9f3f5fa 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nym-cli-commands" version = "1.0.0" -authors = ["Nym Technologies SA"] +authors.workspace = true edition = "2021" [dependencies] @@ -10,11 +10,12 @@ bip39 = "1.0.1" bs58 = "0.4" comfy-table = "6.0.0" cfg-if = "1.0.0" -clap = { version = "3.2", features = ["derive"] } +clap = { version = "4.0", features = ["derive"] } +cw-utils = { workspace = true } handlebars = "3.0.1" humantime-serde = "1.0" k256 = { version = "0.10", features = ["ecdsa", "sha256"] } -log = "0.4" +log = { workspace = true } rand = {version = "0.6", features = ["std"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1" @@ -25,9 +26,12 @@ url = "2.2" tap = "1" cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } -cosmwasm-std = { version = "1.0.0" } +cosmwasm-std = { workspace = true } -validator-client = { path = "../client-libs/validator-client", features = ["nymd-client"] } -network-defaults = { path = "../network-defaults" } -mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } -vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-contract" } +validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] } +nym-network-defaults = { path = "../network-defaults" } +nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } +nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-contract" } +nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" } +nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" } +nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" } diff --git a/common/commands/src/context/errors.rs b/common/commands/src/context/errors.rs index 44a99f49ed..c385e96626 100644 --- a/common/commands/src/context/errors.rs +++ b/common/commands/src/context/errors.rs @@ -11,14 +11,14 @@ pub enum ContextError { #[error("failed to parse mnemonic - {0}")] Bip39Error(#[from] bip39::Error), - // there are lots of error that can occur in the nymd client, so just pass through their display details + // there are lots of error that can occur in the nyxd client, so just pass through their display details // TODO: improve this to return known errors #[error("failed to create client - {0}")] - NymdError(String), + NyxdError(String), - #[error("{0}")] - NymdErrorPassthrough(#[from] validator_client::nymd::error::NymdError), + #[error(transparent)] + NyxdErrorPassthrough(#[from] validator_client::nyxd::error::NyxdError), - #[error("{0}")] + #[error(transparent)] ValidatorClientError(#[from] validator_client::ValidatorClientError), } diff --git a/common/commands/src/context/mod.rs b/common/commands/src/context/mod.rs index 4f55aa1847..0355f98c30 100644 --- a/common/commands/src/context/mod.rs +++ b/common/commands/src/context/mod.rs @@ -1,29 +1,29 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use network_defaults::{ +use nym_network_defaults::{ setup_env, - var_names::{API_VALIDATOR, MIXNET_CONTRACT_ADDRESS, NYMD_VALIDATOR, VESTING_CONTRACT_ADDRESS}, + var_names::{MIXNET_CONTRACT_ADDRESS, NYM_API, NYXD, VESTING_CONTRACT_ADDRESS}, NymNetworkDetails, }; use tap::prelude::*; -use validator_client::nymd::{self, AccountId, NymdClient, QueryNymdClient, SigningNymdClient}; -pub use validator_client::validator_api::Client as ValidatorApiClient; +pub use validator_client::nym_api::Client as NymApiClient; +use validator_client::nyxd::{self, AccountId, NyxdClient, QueryNyxdClient, SigningNyxdClient}; use crate::context::errors::ContextError; pub mod errors; -pub type SigningClient = validator_client::nymd::NymdClient; -pub type QueryClient = validator_client::nymd::NymdClient; -pub type SigningClientWithValidatorAPI = validator_client::Client; -pub type QueryClientWithValidatorAPI = validator_client::Client; +pub type SigningClient = validator_client::nyxd::NyxdClient; +pub type QueryClient = validator_client::nyxd::NyxdClient; +pub type SigningClientWithNyxd = validator_client::Client; +pub type QueryClientWithNyxd = validator_client::Client; #[derive(Debug)] pub struct ClientArgs { pub config_env_file: Option, - pub nymd_url: Option, - pub validator_api_url: Option, + pub nyxd_url: Option, + pub nym_api_url: Option, pub mnemonic: Option, pub mixnet_contract_address: Option, pub vesting_contract_address: Option, @@ -32,14 +32,14 @@ pub struct ClientArgs { pub fn get_network_details(args: &ClientArgs) -> Result { // let the network defaults crate handle setting up the env vars if the file arg is set, otherwise // it will default to what is already in env vars, falling back to mainnet - setup_env(args.config_env_file.clone()); + setup_env(args.config_env_file.as_ref()); // override the env vars with user supplied arguments, if set - if let Some(nymd_url) = args.nymd_url.as_ref() { - std::env::set_var(NYMD_VALIDATOR, nymd_url); + if let Some(nyxd_url) = args.nyxd_url.as_ref() { + std::env::set_var(NYXD, nyxd_url); } - if let Some(validator_api_url) = args.validator_api_url.as_ref() { - std::env::set_var(API_VALIDATOR, validator_api_url); + if let Some(nym_api_url) = args.nym_api_url.as_ref() { + std::env::set_var(NYM_API, nym_api_url); } if let Some(mixnet_contract_address) = args.mixnet_contract_address.as_ref() { std::env::set_var(MIXNET_CONTRACT_ADDRESS, mixnet_contract_address.to_string()); @@ -58,8 +58,8 @@ pub fn create_signing_client( args: ClientArgs, network_details: &NymNetworkDetails, ) -> Result { - let client_config = nymd::Config::try_from_nym_network_details(network_details) - .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?; + let client_config = nyxd::Config::try_from_nym_network_details(network_details) + .tap_err(|err| log::error!("Failed to get client config - {err}"))?; // get mnemonic let mnemonic = match std::env::var("MNEMONIC") { @@ -71,44 +71,44 @@ pub fn create_signing_client( }, }; - let nymd_url = network_details + let nyxd_url = network_details .endpoints .first() .expect("network details are not defined") - .nymd_url + .nyxd_url .as_str(); - match NymdClient::connect_with_mnemonic(client_config, nymd_url, mnemonic, None) { + match NyxdClient::connect_with_mnemonic(client_config, nyxd_url, mnemonic, None) { Ok(client) => Ok(client), - Err(e) => Err(ContextError::NymdError(format!("{:?}", e))), + Err(e) => Err(ContextError::NyxdError(format!("{e}"))), } } pub fn create_query_client( network_details: &NymNetworkDetails, ) -> Result { - let client_config = nymd::Config::try_from_nym_network_details(network_details) - .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?; + let client_config = nyxd::Config::try_from_nym_network_details(network_details) + .tap_err(|err| log::error!("Failed to get client config - {err}"))?; - let nymd_url = network_details + let nyxd_url = network_details .endpoints .first() .expect("network details are not defined") - .nymd_url + .nyxd_url .as_str(); - match NymdClient::connect(client_config, nymd_url) { + match NyxdClient::connect(client_config, nyxd_url) { Ok(client) => Ok(client), - Err(e) => Err(ContextError::NymdError(format!("{:?}", e))), + Err(e) => Err(ContextError::NyxdError(format!("{e}"))), } } -pub fn create_signing_client_with_validator_api( +pub fn create_signing_client_with_nym_api( args: ClientArgs, network_details: &NymNetworkDetails, -) -> Result { +) -> Result { let client_config = validator_client::Config::try_from_nym_network_details(network_details) - .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?; + .tap_err(|err| log::error!("Failed to get client config - {err}"))?; // get mnemonic let mnemonic = match std::env::var("MNEMONIC") { @@ -122,18 +122,18 @@ pub fn create_signing_client_with_validator_api( match validator_client::client::Client::new_signing(client_config, mnemonic) { Ok(client) => Ok(client), - Err(e) => Err(ContextError::NymdError(format!("{:?}", e))), + Err(e) => Err(ContextError::NyxdError(format!("{e}"))), } } -pub fn create_query_client_with_validator_api( +pub fn create_query_client_with_nym_api( network_details: &NymNetworkDetails, -) -> Result { +) -> Result { let client_config = validator_client::Config::try_from_nym_network_details(network_details) - .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?; + .tap_err(|err| log::error!("Failed to get client config - {err}"))?; match validator_client::client::Client::new_query(client_config) { Ok(client) => Ok(client), - Err(e) => Err(ContextError::NymdError(format!("{:?}", e))), + Err(e) => Err(ContextError::NyxdError(format!("{e}"))), } } diff --git a/common/commands/src/utils.rs b/common/commands/src/utils.rs index 2e042da300..682465b010 100644 --- a/common/commands/src/utils.rs +++ b/common/commands/src/utils.rs @@ -6,7 +6,7 @@ use std::fmt::Display; use cosmwasm_std::{Coin as CosmWasmCoin, Decimal}; use log::error; -use validator_client::nymd::Coin; +use validator_client::nyxd::Coin; pub fn pretty_coin(coin: &Coin) -> String { let amount = Decimal::from_ratio(coin.amount, 1_000_000u128); @@ -15,7 +15,7 @@ pub fn pretty_coin(coin: &Coin) -> String { } else { &coin.denom }; - format!("{} {}", amount, denom) + format!("{amount} {denom}") } pub fn pretty_cosmwasm_coin(coin: &CosmWasmCoin) -> String { @@ -25,12 +25,12 @@ pub fn pretty_cosmwasm_coin(coin: &CosmWasmCoin) -> String { } else { &coin.denom }; - format!("{} {}", amount, denom) + format!("{amount} {denom}") } pub fn pretty_decimal_with_denom(value: Decimal, denom: &str) -> String { // TODO: we might have to truncate the value here (that's why I moved it to separate function) - format!("{} {}", value, denom) + format!("{value} {denom}") } pub fn show_error(e: E) diff --git a/common/commands/src/validator/account/balance.rs b/common/commands/src/validator/account/balance.rs index eb8e2dfa8f..13a00827ce 100644 --- a/common/commands/src/validator/account/balance.rs +++ b/common/commands/src/validator/account/balance.rs @@ -4,7 +4,7 @@ use clap::Parser; use log::{error, info}; -use validator_client::nymd::AccountId; +use validator_client::nyxd::AccountId; use crate::context::QueryClient; use crate::utils::{pretty_coin, show_error}; @@ -57,7 +57,7 @@ pub async fn query_balance( if denom.is_empty() || denom.eq_ignore_ascii_case(&coin.denom) { if args.raw { if !args.hide_denom { - println!("{}", coin); + println!("{coin}"); } else { println!("{}", coin.amount); } diff --git a/common/commands/src/validator/account/create.rs b/common/commands/src/validator/account/create.rs index 3003b9a9d5..e82e97ffde 100644 --- a/common/commands/src/validator/account/create.rs +++ b/common/commands/src/validator/account/create.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use clap::Parser; -use validator_client::nymd::wallet::DirectSecp256k1HdWallet; +use validator_client::nyxd::wallet::DirectSecp256k1HdWallet; #[derive(Debug, Parser)] pub struct Args { @@ -15,8 +15,7 @@ pub fn create_account(args: Args, prefix: &str) { let word_count = args.word_count.unwrap_or(24); let mnemonic = bip39::Mnemonic::generate(word_count).expect("failed to generate mnemonic!"); - let wallet = - DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic).expect("failed to build wallet!"); + let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); // Output address and mnemonics into separate lines for easier parsing println!("{}", wallet.mnemonic()); diff --git a/common/commands/src/validator/account/pubkey.rs b/common/commands/src/validator/account/pubkey.rs index 83d15f25e3..8f340229f9 100644 --- a/common/commands/src/validator/account/pubkey.rs +++ b/common/commands/src/validator/account/pubkey.rs @@ -4,8 +4,8 @@ use clap::Parser; use log::{error, info}; -use validator_client::nymd::wallet::DirectSecp256k1HdWallet; -use validator_client::nymd::AccountId; +use validator_client::nyxd::wallet::DirectSecp256k1HdWallet; +use validator_client::nyxd::AccountId; use crate::context::QueryClient; use crate::utils::show_error; @@ -51,21 +51,19 @@ pub async fn get_pubkey( } pub fn get_pubkey_from_mnemonic(address: AccountId, prefix: &str, mnemonic: bip39::Mnemonic) { - match DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic) { - Ok(wallet) => match wallet.try_derive_accounts() { - Ok(accounts) => match accounts.iter().find(|a| *a.address() == address) { - Some(account) => { - println!("{}", account.public_key().to_string()); - } - None => { - error!("Could not derive key that matches {}", address) - } - }, - Err(e) => { - error!("Failed to derive accounts. {}", e); + let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); + match wallet.try_derive_accounts() { + Ok(accounts) => match accounts.iter().find(|a| *a.address() == address) { + Some(account) => { + println!("{}", account.public_key().to_string()); + } + None => { + error!("Could not derive key that matches {}", address) } }, - Err(e) => show_error(e), + Err(e) => { + error!("Failed to derive accounts. {}", e); + } } } @@ -77,12 +75,12 @@ pub async fn get_pubkey_from_chain(address: AccountId, client: &QueryClient) { if let Some(pubkey) = base_account.pubkey { println!("{}", pubkey.to_string()); } else { - println!("No account associated with address {}", address); + println!("No account associated with address {address}"); } } } Ok(None) => { - println!("No account associated with address {}", address); + println!("No account associated with address {address}"); } Err(e) => show_error(e), } diff --git a/common/commands/src/validator/account/send.rs b/common/commands/src/validator/account/send.rs index 90816258bd..ac80de8169 100644 --- a/common/commands/src/validator/account/send.rs +++ b/common/commands/src/validator/account/send.rs @@ -5,7 +5,7 @@ use clap::Parser; use log::info; use serde_json::json; -use validator_client::nymd::{AccountId, Coin}; +use validator_client::nyxd::{AccountId, Coin}; use crate::context::SigningClient; diff --git a/common/commands/src/validator/cosmwasm/execute_contract.rs b/common/commands/src/validator/cosmwasm/execute_contract.rs index f387b83903..d271121a49 100644 --- a/common/commands/src/validator/cosmwasm/execute_contract.rs +++ b/common/commands/src/validator/cosmwasm/execute_contract.rs @@ -6,7 +6,7 @@ use clap::Parser; use cosmrs::AccountId; use log::{error, info}; use serde_json::{json, Value}; -use validator_client::nymd::Coin; +use validator_client::nyxd::Coin; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/cosmwasm/generators/coconut_bandwidth.rs b/common/commands/src/validator/cosmwasm/generators/coconut_bandwidth.rs new file mode 100644 index 0000000000..2640a5e89b --- /dev/null +++ b/common/commands/src/validator/cosmwasm/generators/coconut_bandwidth.rs @@ -0,0 +1,52 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::str::FromStr; + +use clap::Parser; +use log::{debug, info}; + +use nym_coconut_bandwidth_contract_common::msg::InstantiateMsg; +use validator_client::nyxd::AccountId; + +#[derive(Debug, Parser)] +pub struct Args { + #[clap(long)] + pub pool_addr: String, + + #[clap(long)] + pub multisig_addr: Option, + + #[clap(long)] + pub mix_denom: Option, +} + +pub async fn generate(args: Args) { + info!("Starting to generate vesting contract instantiate msg"); + + debug!("Received arguments: {:?}", args); + + let multisig_addr = args.multisig_addr.unwrap_or_else(|| { + let address = std::env::var(nym_network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS) + .expect("Multisig address has to be set"); + AccountId::from_str(address.as_str()) + .expect("Failed converting multisig address to AccountId") + }); + + let mix_denom = args.mix_denom.unwrap_or_else(|| { + std::env::var(nym_network_defaults::var_names::MIX_DENOM).expect("Mix denom has to be set") + }); + + let instantiate_msg = InstantiateMsg { + pool_addr: args.pool_addr, + multisig_addr: multisig_addr.to_string(), + mix_denom, + }; + + debug!("instantiate_msg: {:?}", instantiate_msg); + + let res = + serde_json::to_string(&instantiate_msg).expect("failed to convert instantiate msg to json"); + + println!("{res}") +} diff --git a/common/commands/src/validator/cosmwasm/generators/coconut_dkg.rs b/common/commands/src/validator/cosmwasm/generators/coconut_dkg.rs new file mode 100644 index 0000000000..6a1294fe16 --- /dev/null +++ b/common/commands/src/validator/cosmwasm/generators/coconut_dkg.rs @@ -0,0 +1,104 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Parser; +use log::{debug, info}; +use std::str::FromStr; + +use nym_coconut_dkg_common::msg::InstantiateMsg; +use nym_coconut_dkg_common::types::TimeConfiguration; +use validator_client::nyxd::AccountId; + +#[derive(Debug, Parser)] +pub struct Args { + #[clap(long)] + pub group_addr: String, + + #[clap(long)] + pub multisig_addr: Option, + + #[clap(long)] + pub public_key_submission_time_secs: Option, + + #[clap(long)] + pub dealing_exchange_time_secs: Option, + + #[clap(long)] + pub verification_key_submission_time_secs: Option, + + #[clap(long)] + pub verification_key_validation_time_secs: Option, + + #[clap(long)] + pub verification_key_finalization_time_secs: Option, + + #[clap(long)] + pub in_progress_time_secs: Option, + + #[clap(long)] + pub mix_denom: Option, +} + +pub async fn generate(args: Args) { + info!("Starting to generate vesting contract instantiate msg"); + + debug!("Received arguments: {:?}", args); + + let multisig_addr = args.multisig_addr.unwrap_or_else(|| { + let address = std::env::var(nym_network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS) + .expect("Multisig address has to be set"); + AccountId::from_str(address.as_str()) + .expect("Failed converting multisig address to AccountId") + }); + + let mix_denom = args.mix_denom.unwrap_or_else(|| { + std::env::var(nym_network_defaults::var_names::MIX_DENOM).expect("Mix denom has to be set") + }); + + let mut time_configuration = if let Ok(config) = + std::env::var(nym_network_defaults::var_names::DKG_TIME_CONFIGURATION) + { + TimeConfiguration::from_str(&config).expect("Invalid env variable value") + } else { + TimeConfiguration::default() + }; + if let Some(public_key_submission_time_secs) = args.public_key_submission_time_secs { + time_configuration.public_key_submission_time_secs = public_key_submission_time_secs; + } + if let Some(dealing_exchange_time_secs) = args.dealing_exchange_time_secs { + time_configuration.dealing_exchange_time_secs = dealing_exchange_time_secs; + } + if let Some(verification_key_submission_time_secs) = args.verification_key_submission_time_secs + { + time_configuration.verification_key_submission_time_secs = + verification_key_submission_time_secs; + } + if let Some(verification_key_validation_time_secs) = args.verification_key_validation_time_secs + { + time_configuration.verification_key_validation_time_secs = + verification_key_validation_time_secs; + } + if let Some(verification_key_finalization_time_secs) = + args.verification_key_finalization_time_secs + { + time_configuration.verification_key_finalization_time_secs = + verification_key_finalization_time_secs; + } + if let Some(in_progress_time_secs) = args.in_progress_time_secs { + time_configuration.in_progress_time_secs = in_progress_time_secs; + } + + let instantiate_msg = InstantiateMsg { + group_addr: args.group_addr, + multisig_addr: multisig_addr.to_string(), + time_configuration: Some(time_configuration), + mix_denom, + }; + + debug!("instantiate_msg: {:?}", instantiate_msg); + + let res = + serde_json::to_string(&instantiate_msg).expect("failed to convert instantiate msg to json"); + + println!("{res}") +} diff --git a/common/commands/src/validator/cosmwasm/generators/mixnet.rs b/common/commands/src/validator/cosmwasm/generators/mixnet.rs new file mode 100644 index 0000000000..4579385dd3 --- /dev/null +++ b/common/commands/src/validator/cosmwasm/generators/mixnet.rs @@ -0,0 +1,115 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Parser; +use log::{debug, info}; + +use cosmwasm_std::Decimal; +use nym_mixnet_contract_common::{InitialRewardingParams, InstantiateMsg, Percent}; +use std::str::FromStr; +use std::time::Duration; +use validator_client::nyxd::AccountId; + +#[derive(Debug, Parser)] +pub struct Args { + #[clap(long)] + pub rewarding_validator_address: Option, + + #[clap(long)] + pub vesting_contract_address: Option, + + #[clap(long)] + pub rewarding_denom: Option, + + #[clap(long, default_value_t = 720)] + pub epochs_in_interval: u32, + + #[clap(long, default_value_t = 60*60)] + pub epoch_duration: u64, + + #[clap(long, default_value_t = 244_817_525_850_285)] + pub initial_reward_pool: u128, + + #[clap(long, default_value_t = 100_000_000_000_000)] + pub initial_staking_supply: u128, + + #[clap(long, default_value_t = 50)] + pub staking_supply_scale_factor: u64, + + #[clap(long, default_value_t = 30)] + pub sybil_resistance: u64, + + #[clap(long, default_value_t = 10)] + pub active_set_work_factor: u32, + + #[clap(long, default_value_t = 2)] + pub interval_pool_emission: u64, + + #[clap(long, default_value_t = 240)] + pub rewarded_set_size: u32, + + #[clap(long, default_value_t = 240)] + pub active_set_size: u32, +} + +pub async fn generate(args: Args) { + info!("Starting to generate mixnet contract instantiate msg"); + + debug!("Received arguments: {:?}", args); + + let initial_rewarding_params = InitialRewardingParams { + initial_reward_pool: Decimal::from_atomics(args.initial_reward_pool, 0) + .expect("initial_rewarding_pool can't be converted to Decimal"), + initial_staking_supply: Decimal::from_atomics(args.initial_staking_supply, 0) + .expect("initial_staking_supply can't be converted to Decimal"), + staking_supply_scale_factor: Percent::from_percentage_value( + args.staking_supply_scale_factor, + ) + .unwrap(), + sybil_resistance: Percent::from_percentage_value(args.sybil_resistance) + .expect("sybil_resistance can't be converted to Percent"), + active_set_work_factor: Decimal::from_atomics(args.active_set_work_factor, 0) + .expect("active_set_work_factor can't be converted to Decimal"), + interval_pool_emission: Percent::from_percentage_value(args.interval_pool_emission) + .expect("interval_pool_emission can't be converted to Percent"), + rewarded_set_size: args.rewarded_set_size, + active_set_size: args.active_set_size, + }; + + debug!("initial_rewarding_params: {:?}", initial_rewarding_params); + + let rewarding_validator_address = args.rewarding_validator_address.unwrap_or_else(|| { + let address = std::env::var(nym_network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS) + .expect("Rewarding validator address has to be set"); + AccountId::from_str(address.as_str()) + .expect("Failed converting rewarding validator address to AccountId") + }); + + let vesting_contract_address = args.vesting_contract_address.unwrap_or_else(|| { + let address = std::env::var(nym_network_defaults::var_names::VESTING_CONTRACT_ADDRESS) + .expect("Vesting contract address has to be set"); + AccountId::from_str(address.as_str()) + .expect("Failed converting vesting contract address to AccountId") + }); + + let rewarding_denom = args.rewarding_denom.unwrap_or_else(|| { + std::env::var(nym_network_defaults::var_names::MIX_DENOM) + .expect("Rewarding (mix) denom has to be set") + }); + + let instantiate_msg = InstantiateMsg { + rewarding_validator_address: rewarding_validator_address.to_string(), + vesting_contract_address: vesting_contract_address.to_string(), + rewarding_denom, + epochs_in_interval: args.epochs_in_interval, + epoch_duration: Duration::from_secs(args.epoch_duration), + initial_rewarding_params, + }; + + debug!("instantiate_msg: {:?}", instantiate_msg); + + let res = + serde_json::to_string(&instantiate_msg).expect("failed to convert instantiate msg to json"); + + println!("{res}") +} diff --git a/common/commands/src/validator/cosmwasm/generators/mod.rs b/common/commands/src/validator/cosmwasm/generators/mod.rs new file mode 100644 index 0000000000..85bd5a93e2 --- /dev/null +++ b/common/commands/src/validator/cosmwasm/generators/mod.rs @@ -0,0 +1,26 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::{Args, Subcommand}; + +pub mod coconut_bandwidth; +pub mod coconut_dkg; +pub mod mixnet; +pub mod multisig; +pub mod vesting; + +#[derive(Debug, Args)] +#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] +pub struct GenerateMessage { + #[clap(subcommand)] + pub command: GenerateMessageCommands, +} + +#[derive(Debug, Subcommand)] +pub enum GenerateMessageCommands { + CoconutBandwidth(coconut_bandwidth::Args), + CoconutDKG(coconut_dkg::Args), + Mixnet(mixnet::Args), + Multisig(multisig::Args), + Vesting(vesting::Args), +} diff --git a/common/commands/src/validator/cosmwasm/generators/multisig.rs b/common/commands/src/validator/cosmwasm/generators/multisig.rs new file mode 100644 index 0000000000..3db5af2022 --- /dev/null +++ b/common/commands/src/validator/cosmwasm/generators/multisig.rs @@ -0,0 +1,69 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::str::FromStr; + +use clap::Parser; +use cosmwasm_std::Decimal; +use cw_utils::{Duration, Threshold}; +use log::{debug, info}; +use nym_multisig_contract_common::msg::InstantiateMsg; +use validator_client::nyxd::AccountId; + +#[derive(Debug, Parser)] +pub struct Args { + #[clap(long)] + pub group_addr: String, + + #[clap(long, default_value_t = 67)] + pub threshold: u128, + + #[clap(long, default_value_t = 3600)] + pub max_voting_period: u64, + + #[clap(long)] + pub coconut_bandwidth_contract_address: Option, + + #[clap(long)] + pub coconut_dkg_contract_address: Option, +} + +pub async fn generate(args: Args) { + info!("Starting to generate vesting contract instantiate msg"); + + debug!("Received arguments: {:?}", args); + + let coconut_bandwidth_contract_address = + args.coconut_bandwidth_contract_address.unwrap_or_else(|| { + let address = + std::env::var(nym_network_defaults::var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS) + .expect("Coconut bandwidth contract address has to be set"); + AccountId::from_str(address.as_str()) + .expect("Failed converting bandwidth contract address to AccountId") + }); + + let coconut_dkg_contract_address = args.coconut_dkg_contract_address.unwrap_or_else(|| { + let address = std::env::var(nym_network_defaults::var_names::COCONUT_DKG_CONTRACT_ADDRESS) + .expect("Coconut DKG contract address has to be set"); + AccountId::from_str(address.as_str()) + .expect("Failed converting DKG contract address to AccountId") + }); + + let instantiate_msg = InstantiateMsg { + group_addr: args.group_addr, + threshold: Threshold::AbsolutePercentage { + percentage: Decimal::from_atomics(args.threshold, 2) + .expect("threshold can't be converted to Decimal"), + }, + max_voting_period: Duration::Time(args.max_voting_period), + coconut_bandwidth_contract_address: coconut_bandwidth_contract_address.to_string(), + coconut_dkg_contract_address: coconut_dkg_contract_address.to_string(), + }; + + debug!("instantiate_msg: {:?}", instantiate_msg); + + let res = + serde_json::to_string(&instantiate_msg).expect("failed to convert instantiate msg to json"); + + println!("{res}") +} diff --git a/common/commands/src/validator/cosmwasm/generators/vesting.rs b/common/commands/src/validator/cosmwasm/generators/vesting.rs new file mode 100644 index 0000000000..d9946f8ebb --- /dev/null +++ b/common/commands/src/validator/cosmwasm/generators/vesting.rs @@ -0,0 +1,48 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::str::FromStr; + +use clap::Parser; +use log::{debug, info}; + +use nym_vesting_contract_common::InitMsg; +use validator_client::nyxd::AccountId; + +#[derive(Debug, Parser)] +pub struct Args { + #[clap(long)] + pub mixnet_contract_address: Option, + + #[clap(long)] + pub mix_denom: Option, +} + +pub async fn generate(args: Args) { + info!("Starting to generate vesting contract instantiate msg"); + + debug!("Received arguments: {:?}", args); + + let mixnet_contract_address = args.mixnet_contract_address.unwrap_or_else(|| { + let address = std::env::var(nym_network_defaults::var_names::MIXNET_CONTRACT_ADDRESS) + .expect("Mixnet contract address has to be set"); + AccountId::from_str(address.as_str()) + .expect("Failed converting mixnet address to AccountId") + }); + + let mix_denom = args.mix_denom.unwrap_or_else(|| { + std::env::var(nym_network_defaults::var_names::MIX_DENOM).expect("Mix denom has to be set") + }); + + let instantiate_msg = InitMsg { + mixnet_contract_address: mixnet_contract_address.to_string(), + mix_denom, + }; + + debug!("instantiate_msg: {:?}", instantiate_msg); + + let res = + serde_json::to_string(&instantiate_msg).expect("failed to convert instantiate msg to json"); + + println!("{res}") +} diff --git a/common/commands/src/validator/cosmwasm/init_contract.rs b/common/commands/src/validator/cosmwasm/init_contract.rs index c624a81a94..c85e5cd462 100644 --- a/common/commands/src/validator/cosmwasm/init_contract.rs +++ b/common/commands/src/validator/cosmwasm/init_contract.rs @@ -5,9 +5,9 @@ use crate::context::SigningClient; use clap::Parser; use cosmrs::{AccountId, Coin as CosmosCoin}; use log::info; -use network_defaults::NymNetworkDetails; -use validator_client::nymd::cosmwasm_client::types::{ContractCodeId, InstantiateOptions}; -use validator_client::nymd::Coin; +use nym_network_defaults::NymNetworkDetails; +use validator_client::nyxd::cosmwasm_client::types::{ContractCodeId, InstantiateOptions}; +use validator_client::nyxd::Coin; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/cosmwasm/migrate_contract.rs b/common/commands/src/validator/cosmwasm/migrate_contract.rs index fa21062ee7..4abf101ae8 100644 --- a/common/commands/src/validator/cosmwasm/migrate_contract.rs +++ b/common/commands/src/validator/cosmwasm/migrate_contract.rs @@ -6,7 +6,7 @@ use crate::utils::show_error_passthrough; use clap::Parser; use cosmrs::AccountId; use log::info; -use validator_client::nymd::cosmwasm_client::types::{ContractCodeId, EmptyMsg}; +use validator_client::nyxd::cosmwasm_client::types::{ContractCodeId, EmptyMsg}; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/cosmwasm/mod.rs b/common/commands/src/validator/cosmwasm/mod.rs index 9ee1fa182f..43dcf08951 100644 --- a/common/commands/src/validator/cosmwasm/mod.rs +++ b/common/commands/src/validator/cosmwasm/mod.rs @@ -4,6 +4,7 @@ use clap::{Args, Subcommand}; pub mod execute_contract; +pub mod generators; pub mod init_contract; pub mod migrate_contract; pub mod upload_contract; @@ -21,6 +22,8 @@ pub enum CosmwasmCommands { Upload(crate::validator::cosmwasm::upload_contract::Args), /// Init a WASM smart contract Init(crate::validator::cosmwasm::init_contract::Args), + /// Generate an instantiate message + GenerateInitMessage(generators::GenerateMessage), /// Migrate a WASM smart contract Migrate(crate::validator::cosmwasm::migrate_contract::Args), /// Execute a WASM smart contract method diff --git a/common/commands/src/validator/mixnet/delegators/delegate_to_mixnode.rs b/common/commands/src/validator/mixnet/delegators/delegate_to_mixnode.rs index 50b4880c6e..ebb4db143f 100644 --- a/common/commands/src/validator/mixnet/delegators/delegate_to_mixnode.rs +++ b/common/commands/src/validator/mixnet/delegators/delegate_to_mixnode.rs @@ -4,8 +4,8 @@ use crate::context::SigningClient; use clap::Parser; use log::info; -use mixnet_contract_common::{Coin, MixId}; -use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; +use nym_mixnet_contract_common::{Coin, MixId}; +use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient}; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs b/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs index d816e65a0c..4e6d91926b 100644 --- a/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs +++ b/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs @@ -4,29 +4,29 @@ use clap::Parser; use log::info; -use crate::context::SigningClientWithValidatorAPI; +use crate::context::SigningClientWithNyxd; use crate::utils::{pretty_cosmwasm_coin, show_error_passthrough}; use comfy_table::Table; use cosmwasm_std::Addr; -use mixnet_contract_common::{Delegation, PendingEpochEvent, PendingEpochEventKind}; +use nym_mixnet_contract_common::{Delegation, PendingEpochEvent, PendingEpochEventKind}; #[derive(Debug, Parser)] pub struct Args {} -pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) { +pub async fn execute(_args: Args, client: SigningClientWithNyxd) { info!( "Getting delegations for account {}...", - client.nymd.address() + client.nyxd.address() ); let delegations = client - .get_all_delegator_delegations(client.nymd.address()) + .get_all_delegator_delegations(client.nyxd.address()) .await .map_err(show_error_passthrough); let mixnet_contract_events = client - .get_all_nymd_pending_epoch_events() + .get_all_nyxd_pending_epoch_events() .await .map_err(show_error_passthrough); @@ -48,14 +48,14 @@ pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) { } } -async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidatorAPI) -> String { - match client.nymd.get_block_timestamp(Some(block_height)).await { +async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithNyxd) -> String { + match client.nyxd.get_block_timestamp(Some(block_height)).await { Ok(res) => res.to_rfc3339(), Err(_e) => "-".to_string(), } } -async fn print_delegations(delegations: Vec, client: &SigningClientWithValidatorAPI) { +async fn print_delegations(delegations: Vec, client: &SigningClientWithNyxd) { let mut table = Table::new(); table.set_header(vec!["Timestamp", "Mix Id", "Delegation", "Proxy"]); @@ -75,10 +75,7 @@ async fn print_delegations(delegations: Vec, client: &SigningClientW println!("{table}"); } -async fn print_delegation_events( - events: Vec, - client: &SigningClientWithValidatorAPI, -) { +async fn print_delegation_events(events: Vec, client: &SigningClientWithNyxd) { let mut table = Table::new(); table.set_header(vec![ @@ -97,7 +94,7 @@ async fn print_delegation_events( amount, proxy, } => { - if owner.as_str() == client.nymd.address().as_ref() { + if owner.as_str() == client.nyxd.address().as_ref() { table.add_row(vec![ "not-sure-if-applicable".into(), mix_id.to_string(), @@ -112,7 +109,7 @@ async fn print_delegation_events( mix_id, proxy, } => { - if owner.as_str() == client.nymd.address().as_ref() { + if owner.as_str() == client.nyxd.address().as_ref() { table.add_row(vec![ "not-sure-if-applicable".into(), mix_id.to_string(), diff --git a/common/commands/src/validator/mixnet/delegators/rewards/claim_delegator_reward.rs b/common/commands/src/validator/mixnet/delegators/rewards/claim_delegator_reward.rs index 1858b7cebb..efdd9f4f6c 100644 --- a/common/commands/src/validator/mixnet/delegators/rewards/claim_delegator_reward.rs +++ b/common/commands/src/validator/mixnet/delegators/rewards/claim_delegator_reward.rs @@ -4,8 +4,8 @@ use crate::context::SigningClient; use clap::Parser; use log::info; -use mixnet_contract_common::MixId; -use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; +use nym_mixnet_contract_common::MixId; +use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient}; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/mixnet/delegators/rewards/vesting_claim_delegator_reward.rs b/common/commands/src/validator/mixnet/delegators/rewards/vesting_claim_delegator_reward.rs index 6eca5cbf7a..bb30d115c4 100644 --- a/common/commands/src/validator/mixnet/delegators/rewards/vesting_claim_delegator_reward.rs +++ b/common/commands/src/validator/mixnet/delegators/rewards/vesting_claim_delegator_reward.rs @@ -4,8 +4,8 @@ use crate::context::SigningClient; use clap::Parser; use log::info; -use mixnet_contract_common::MixId; -use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; +use nym_mixnet_contract_common::MixId; +use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient}; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/mixnet/delegators/undelegate_from_mixnode.rs b/common/commands/src/validator/mixnet/delegators/undelegate_from_mixnode.rs index 063d4a523f..e36fac1c73 100644 --- a/common/commands/src/validator/mixnet/delegators/undelegate_from_mixnode.rs +++ b/common/commands/src/validator/mixnet/delegators/undelegate_from_mixnode.rs @@ -4,8 +4,8 @@ use crate::context::SigningClient; use clap::Parser; use log::info; -use mixnet_contract_common::MixId; -use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; +use nym_mixnet_contract_common::MixId; +use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient}; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/mixnet/delegators/vesting_delegate_to_mixnode.rs b/common/commands/src/validator/mixnet/delegators/vesting_delegate_to_mixnode.rs index ba33a49037..5d6b5f0547 100644 --- a/common/commands/src/validator/mixnet/delegators/vesting_delegate_to_mixnode.rs +++ b/common/commands/src/validator/mixnet/delegators/vesting_delegate_to_mixnode.rs @@ -4,9 +4,9 @@ use clap::Parser; use log::info; -use mixnet_contract_common::{Coin, MixId}; -use validator_client::nymd::traits::MixnetQueryClient; -use validator_client::nymd::VestingSigningClient; +use nym_mixnet_contract_common::{Coin, MixId}; +use validator_client::nyxd::traits::MixnetQueryClient; +use validator_client::nyxd::VestingSigningClient; use crate::context::SigningClient; @@ -18,6 +18,9 @@ pub struct Args { #[clap(long)] pub identity_key: Option, + #[clap(long)] + pub on_behalf_of: Option, + #[clap(long)] pub amount: u128, } @@ -45,7 +48,7 @@ pub async fn vesting_delegate_to_mixnode(args: Args, client: SigningClient) { let coin = Coin::new(args.amount, denom); let res = client - .vesting_delegate_to_mixnode(mix_id, coin.into(), None) + .vesting_delegate_to_mixnode(mix_id, coin.into(), args.on_behalf_of, None) .await .expect("failed to delegate to mixnode!"); diff --git a/common/commands/src/validator/mixnet/delegators/vesting_undelegate_from_mixnode.rs b/common/commands/src/validator/mixnet/delegators/vesting_undelegate_from_mixnode.rs index 59fb6b6c3d..a333df365f 100644 --- a/common/commands/src/validator/mixnet/delegators/vesting_undelegate_from_mixnode.rs +++ b/common/commands/src/validator/mixnet/delegators/vesting_undelegate_from_mixnode.rs @@ -3,9 +3,9 @@ use clap::Parser; use log::info; -use mixnet_contract_common::MixId; -use validator_client::nymd::traits::MixnetQueryClient; -use validator_client::nymd::VestingSigningClient; +use nym_mixnet_contract_common::MixId; +use validator_client::nyxd::traits::MixnetQueryClient; +use validator_client::nyxd::VestingSigningClient; use crate::context::SigningClient; @@ -16,6 +16,9 @@ pub struct Args { #[clap(long)] pub identity_key: Option, + + #[clap(long)] + pub on_behalf_of: Option, } pub async fn vesting_undelegate_from_mixnode(args: Args, client: SigningClient) { @@ -37,7 +40,7 @@ pub async fn vesting_undelegate_from_mixnode(args: Args, client: SigningClient) }; let res = client - .vesting_undelegate_from_mixnode(mix_id, None) + .vesting_undelegate_from_mixnode(mix_id, args.on_behalf_of, None) .await .expect("failed to remove stake from vesting account on mixnode!"); diff --git a/common/commands/src/validator/mixnet/operators/gateway/bond_gateway.rs b/common/commands/src/validator/mixnet/operators/gateway/bond_gateway.rs index e9482dde34..e565364609 100644 --- a/common/commands/src/validator/mixnet/operators/gateway/bond_gateway.rs +++ b/common/commands/src/validator/mixnet/operators/gateway/bond_gateway.rs @@ -4,9 +4,9 @@ use crate::context::SigningClient; use clap::Parser; use log::{info, warn}; -use mixnet_contract_common::Coin; -use network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT}; -use validator_client::nymd::traits::MixnetSigningClient; +use nym_mixnet_contract_common::Coin; +use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT}; +use validator_client::nyxd::traits::MixnetSigningClient; #[derive(Debug, Parser)] pub struct Args { @@ -55,7 +55,7 @@ pub async fn bond_gateway(args: Args, client: SigningClient) { return; } - let gateway = mixnet_contract_common::Gateway { + let gateway = nym_mixnet_contract_common::Gateway { host: args.host, mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT), clients_port: args.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT), diff --git a/common/commands/src/validator/mixnet/operators/gateway/unbond_gateway.rs b/common/commands/src/validator/mixnet/operators/gateway/unbond_gateway.rs index 90cae1623c..e0e7ba68f1 100644 --- a/common/commands/src/validator/mixnet/operators/gateway/unbond_gateway.rs +++ b/common/commands/src/validator/mixnet/operators/gateway/unbond_gateway.rs @@ -4,7 +4,7 @@ use crate::context::SigningClient; use clap::Parser; use log::info; -use validator_client::nymd::traits::MixnetSigningClient; +use validator_client::nyxd::traits::MixnetSigningClient; #[derive(Debug, Parser)] pub struct Args {} diff --git a/common/commands/src/validator/mixnet/operators/gateway/vesting_bond_gateway.rs b/common/commands/src/validator/mixnet/operators/gateway/vesting_bond_gateway.rs index 6c26b09eeb..9cb4b338d4 100644 --- a/common/commands/src/validator/mixnet/operators/gateway/vesting_bond_gateway.rs +++ b/common/commands/src/validator/mixnet/operators/gateway/vesting_bond_gateway.rs @@ -4,9 +4,9 @@ use crate::context::SigningClient; use clap::Parser; use log::{info, warn}; -use mixnet_contract_common::{Coin, Gateway}; -use network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT}; -use validator_client::nymd::VestingSigningClient; +use nym_mixnet_contract_common::{Coin, Gateway}; +use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT}; +use validator_client::nyxd::VestingSigningClient; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/mixnet/operators/gateway/vesting_unbond_gateway.rs b/common/commands/src/validator/mixnet/operators/gateway/vesting_unbond_gateway.rs index 0c0eae2fe6..c9b95a3169 100644 --- a/common/commands/src/validator/mixnet/operators/gateway/vesting_unbond_gateway.rs +++ b/common/commands/src/validator/mixnet/operators/gateway/vesting_unbond_gateway.rs @@ -4,7 +4,7 @@ use crate::context::SigningClient; use clap::Parser; use log::info; -use validator_client::nymd::traits::MixnetSigningClient; +use validator_client::nyxd::traits::MixnetSigningClient; #[derive(Debug, Parser)] pub struct Args {} diff --git a/common/commands/src/validator/mixnet/operators/mixnode/bond_mixnode.rs b/common/commands/src/validator/mixnet/operators/mixnode/bond_mixnode.rs index 4d672a5322..f6650a9ac9 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/bond_mixnode.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/bond_mixnode.rs @@ -5,12 +5,12 @@ use clap::Parser; use cosmwasm_std::Uint128; use log::{info, warn}; -use mixnet_contract_common::{Coin, MixNodeCostParams, Percent}; -use network_defaults::{ +use nym_mixnet_contract_common::{Coin, MixNodeCostParams, Percent}; +use nym_network_defaults::{ DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, }; -use validator_client::nymd::traits::MixnetSigningClient; -use validator_client::nymd::CosmWasmCoin; +use validator_client::nyxd::traits::MixnetSigningClient; +use validator_client::nyxd::CosmWasmCoin; use crate::context::SigningClient; @@ -70,7 +70,7 @@ pub async fn bond_mixnode(args: Args, client: SigningClient) { return; } - let mixnode = mixnet_contract_common::MixNode { + let mixnode = nym_mixnet_contract_common::MixNode { host: args.host, mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT), verloc_port: args.verloc_port.unwrap_or(DEFAULT_VERLOC_LISTENING_PORT), diff --git a/common/commands/src/validator/mixnet/operators/mixnode/keys/decode_mixnode_key.rs b/common/commands/src/validator/mixnet/operators/mixnode/keys/decode_mixnode_key.rs index 5938a672c4..21b026c85e 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/keys/decode_mixnode_key.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/keys/decode_mixnode_key.rs @@ -13,5 +13,5 @@ pub fn decode_mixnode_key(args: Args) { let b64_decoded = base64::decode(args.key).expect("failed to decode base64 string"); let b58_encoded = bs58::encode(&b64_decoded).into_string(); - println!("{}", b58_encoded) + println!("{b58_encoded}") } diff --git a/common/commands/src/validator/mixnet/operators/mixnode/rewards/claim_operator_reward.rs b/common/commands/src/validator/mixnet/operators/mixnode/rewards/claim_operator_reward.rs index 773f4cb3d7..e689f91f73 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/rewards/claim_operator_reward.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/rewards/claim_operator_reward.rs @@ -4,7 +4,7 @@ use crate::context::SigningClient; use clap::Parser; use log::info; -use validator_client::nymd::traits::MixnetSigningClient; +use validator_client::nyxd::traits::MixnetSigningClient; #[derive(Debug, Parser)] pub struct Args {} diff --git a/common/commands/src/validator/mixnet/operators/mixnode/rewards/vesting_claim_operator_reward.rs b/common/commands/src/validator/mixnet/operators/mixnode/rewards/vesting_claim_operator_reward.rs index d4895e83f2..f91506ad02 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/rewards/vesting_claim_operator_reward.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/rewards/vesting_claim_operator_reward.rs @@ -4,7 +4,7 @@ use crate::context::SigningClient; use clap::Parser; use log::info; -use validator_client::nymd::traits::MixnetSigningClient; +use validator_client::nyxd::traits::MixnetSigningClient; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/mixnet/operators/mixnode/settings/update_config.rs b/common/commands/src/validator/mixnet/operators/mixnode/settings/update_config.rs index 84cf9f2f4e..6afc6bfce9 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/settings/update_config.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/settings/update_config.rs @@ -4,8 +4,8 @@ use crate::context::SigningClient; use clap::Parser; use log::info; -use mixnet_contract_common::MixNodeConfigUpdate; -use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; +use nym_mixnet_contract_common::MixNodeConfigUpdate; +use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient}; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/mixnet/operators/mixnode/settings/vesting_update_config.rs b/common/commands/src/validator/mixnet/operators/mixnode/settings/vesting_update_config.rs index bca3642c97..02531ff493 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/settings/vesting_update_config.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/settings/vesting_update_config.rs @@ -4,9 +4,9 @@ use crate::context::SigningClient; use clap::Parser; use log::info; -use mixnet_contract_common::MixNodeConfigUpdate; -use validator_client::nymd::traits::MixnetQueryClient; -use validator_client::nymd::VestingSigningClient; +use nym_mixnet_contract_common::MixNodeConfigUpdate; +use validator_client::nyxd::traits::MixnetQueryClient; +use validator_client::nyxd::VestingSigningClient; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/mixnet/operators/mixnode/unbond_mixnode.rs b/common/commands/src/validator/mixnet/operators/mixnode/unbond_mixnode.rs index 04fa9a55cd..8f5e881048 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/unbond_mixnode.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/unbond_mixnode.rs @@ -3,7 +3,7 @@ use clap::Parser; use log::info; -use validator_client::nymd::traits::MixnetSigningClient; +use validator_client::nyxd::traits::MixnetSigningClient; use crate::context::SigningClient; diff --git a/common/commands/src/validator/mixnet/operators/mixnode/vesting_bond_mixnode.rs b/common/commands/src/validator/mixnet/operators/mixnode/vesting_bond_mixnode.rs index 6f20996460..a42d3b02de 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/vesting_bond_mixnode.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/vesting_bond_mixnode.rs @@ -5,12 +5,12 @@ use crate::context::SigningClient; use clap::Parser; use cosmwasm_std::Uint128; use log::{info, warn}; -use mixnet_contract_common::{Coin, MixNodeCostParams}; -use mixnet_contract_common::{MixNode, Percent}; -use network_defaults::{ +use nym_mixnet_contract_common::{Coin, MixNodeCostParams}; +use nym_mixnet_contract_common::{MixNode, Percent}; +use nym_network_defaults::{ DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, }; -use validator_client::nymd::{CosmWasmCoin, VestingSigningClient}; +use validator_client::nyxd::{CosmWasmCoin, VestingSigningClient}; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/mixnet/operators/mixnode/vesting_unbond_mixnode.rs b/common/commands/src/validator/mixnet/operators/mixnode/vesting_unbond_mixnode.rs index 0824cc9342..e99c4baf83 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/vesting_unbond_mixnode.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/vesting_unbond_mixnode.rs @@ -4,7 +4,7 @@ use crate::context::SigningClient; use clap::Parser; use log::info; -use validator_client::nymd::VestingSigningClient; +use validator_client::nyxd::VestingSigningClient; #[derive(Debug, Parser)] pub struct Args { diff --git a/common/commands/src/validator/mixnet/query/query_all_gateways.rs b/common/commands/src/validator/mixnet/query/query_all_gateways.rs index 679ac08bcd..17f1d4d48d 100644 --- a/common/commands/src/validator/mixnet/query/query_all_gateways.rs +++ b/common/commands/src/validator/mixnet/query/query_all_gateways.rs @@ -4,7 +4,7 @@ use clap::Parser; use comfy_table::Table; -use crate::context::QueryClientWithValidatorAPI; +use crate::context::QueryClientWithNyxd; use crate::utils::{pretty_cosmwasm_coin, show_error}; #[derive(Debug, Parser)] @@ -14,8 +14,8 @@ pub struct Args { pub identity_key: Option, } -pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) { - match client.validator_api.get_gateways().await { +pub async fn query(args: Args, client: &QueryClientWithNyxd) { + match client.nym_api.get_gateways().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs index 4ec6b25b7b..3b23f2092e 100644 --- a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs +++ b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs @@ -4,7 +4,7 @@ use clap::Parser; use comfy_table::Table; -use crate::context::QueryClientWithValidatorAPI; +use crate::context::QueryClientWithNyxd; use crate::utils::{pretty_decimal_with_denom, show_error}; #[derive(Debug, Parser)] @@ -14,8 +14,8 @@ pub struct Args { pub identity_key: Option, } -pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) { - match client.validator_api.get_mixnodes().await { +pub async fn query(args: Args, client: &QueryClientWithNyxd) { + match client.nym_api.get_mixnodes().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/common/commands/src/validator/signature/errors.rs b/common/commands/src/validator/signature/errors.rs index 2369e06d66..d67c3d387b 100644 --- a/common/commands/src/validator/signature/errors.rs +++ b/common/commands/src/validator/signature/errors.rs @@ -11,6 +11,6 @@ pub enum Errors { #[error("signature error - {0}")] SignatureError(#[from] k256::ecdsa::signature::Error), - #[error("{0}")] + #[error(transparent)] CosmrsError(#[from] cosmrs::ErrorReport), } diff --git a/common/commands/src/validator/signature/sign.rs b/common/commands/src/validator/signature/sign.rs index cd4c169ae1..3dd93b22d5 100644 --- a/common/commands/src/validator/signature/sign.rs +++ b/common/commands/src/validator/signature/sign.rs @@ -1,13 +1,12 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::utils::show_error; use clap::Parser; use cosmrs::crypto::PublicKey; use log::error; use serde::Serialize; use serde_json::json; -use validator_client::nymd::wallet::DirectSecp256k1HdWallet; +use validator_client::nyxd::wallet::DirectSecp256k1HdWallet; #[derive(Debug, Serialize)] pub struct SignatureOutputJson { @@ -36,33 +35,32 @@ pub fn sign(args: Args, prefix: &str, mnemonic: Option) { return; } - match DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.expect("mnemonic not set")) { - Ok(wallet) => match wallet.try_derive_accounts() { - Ok(accounts) => match accounts.first() { - Some(account) => { - let msg = args.message.into_bytes(); - match wallet.sign_raw_with_account(account, &msg) { - Ok(signature) => { - let output = SignatureOutputJson { - account_id: account.address().to_string(), - public_key: account.public_key(), - signature_as_hex: signature.to_string(), - }; - println!("{}", json!(output)); - } - Err(e) => { - error!("Failed to sign message. {}", e); - } + let wallet = + DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.expect("mnemonic not set")); + match wallet.try_derive_accounts() { + Ok(accounts) => match accounts.first() { + Some(account) => { + let msg = args.message.into_bytes(); + match wallet.sign_raw_with_account(account, &msg) { + Ok(signature) => { + let output = SignatureOutputJson { + account_id: account.address().to_string(), + public_key: account.public_key(), + signature_as_hex: signature.to_string(), + }; + println!("{}", json!(output)); + } + Err(e) => { + error!("Failed to sign message. {}", e); } } - None => { - error!("Could not derive an account key from the mnemonic",) - } - }, - Err(e) => { - error!("Failed to derive accounts. {}", e); + } + None => { + error!("Could not derive an account key from the mnemonic",) } }, - Err(e) => show_error(e), + Err(e) => { + error!("Failed to derive accounts. {}", e); + } } } diff --git a/common/commands/src/validator/signature/verify.rs b/common/commands/src/validator/signature/verify.rs index b29f93ca9a..8f84251a1a 100644 --- a/common/commands/src/validator/signature/verify.rs +++ b/common/commands/src/validator/signature/verify.rs @@ -8,7 +8,7 @@ use cosmrs::crypto::PublicKey; use log::{error, info}; use serde_json::json; -use validator_client::nymd::AccountId; +use validator_client::nyxd::AccountId; use crate::context::QueryClient; use crate::validator::signature::helpers::secp256k1_verify_with_public_key; diff --git a/common/commands/src/validator/vesting/balance.rs b/common/commands/src/validator/vesting/balance.rs index 0dff560b9f..5263d106c3 100644 --- a/common/commands/src/validator/vesting/balance.rs +++ b/common/commands/src/validator/vesting/balance.rs @@ -5,7 +5,7 @@ use clap::Parser; use cosmrs::AccountId; use log::{error, info}; -use validator_client::nymd::{Coin, VestingQueryClient}; +use validator_client::nyxd::{Coin, VestingQueryClient}; use crate::context::QueryClient; use crate::utils::show_error; diff --git a/common/commands/src/validator/vesting/create_vesting_schedule.rs b/common/commands/src/validator/vesting/create_vesting_schedule.rs index c7c133673d..dc3cd25624 100644 --- a/common/commands/src/validator/vesting/create_vesting_schedule.rs +++ b/common/commands/src/validator/vesting/create_vesting_schedule.rs @@ -6,13 +6,13 @@ use std::str::FromStr; use clap::Parser; use log::info; -use mixnet_contract_common::Coin; -use network_defaults::NymNetworkDetails; -use validator_client::nymd::AccountId; -use validator_client::nymd::VestingSigningClient; -use validator_client::nymd::{CosmosCoin, Denom}; -use vesting_contract_common::messages::VestingSpecification; -use vesting_contract_common::PledgeCap; +use nym_mixnet_contract_common::Coin; +use nym_network_defaults::NymNetworkDetails; +use nym_vesting_contract_common::messages::VestingSpecification; +use nym_vesting_contract_common::PledgeCap; +use validator_client::nyxd::AccountId; +use validator_client::nyxd::VestingSigningClient; +use validator_client::nyxd::{CosmosCoin, Denom}; use crate::context::SigningClient; diff --git a/common/commands/src/validator/vesting/query_vesting_schedule.rs b/common/commands/src/validator/vesting/query_vesting_schedule.rs index 10a4dc41d3..d3f544c7ee 100644 --- a/common/commands/src/validator/vesting/query_vesting_schedule.rs +++ b/common/commands/src/validator/vesting/query_vesting_schedule.rs @@ -6,7 +6,7 @@ use cosmrs::AccountId; use cosmwasm_std::Coin as CosmWasmCoin; use log::{error, info}; -use validator_client::nymd::{Coin, VestingQueryClient}; +use validator_client::nyxd::{Coin, VestingQueryClient}; use crate::context::QueryClient; use crate::utils::show_error; @@ -53,8 +53,10 @@ pub async fn query(args: Args, client: QueryClient, address_from_mnemonic: Optio // TODO: get better copy text for what these are let vesting_coins = client.vesting_coins(&vesting_address, None).await; - let delegated_vesting = client.delegated_vesting(&vesting_address, None).await; - let delegated_free = client.delegated_free(&vesting_address, None).await; + let delegated = client.get_delegated_coins(&vesting_address).await; + let pledged = client.get_pledged_coins(&vesting_address).await; + let withdrawn = client.get_withdrawn_coins(&vesting_address).await; + let staked = client.get_staked_coins(&vesting_address).await; original_vesting.as_ref().map_or_else(show_error, |res| { println!( @@ -139,11 +141,17 @@ pub async fn query(args: Args, client: QueryClient, address_from_mnemonic: Optio vesting_coins.map_or_else(show_error, |res| { println!("Vesting coins: {} ({})", pretty_coin(&res), res); }); - delegated_vesting.map_or_else(show_error, |res| { - println!("Delegated vesting: {} ({})", pretty_coin(&res), res); + withdrawn.map_or_else(show_error, |res| { + println!("Withdrawn: {} ({})", pretty_coin(&res), res); }); - delegated_free.map_or_else(show_error, |res| { - println!("Delegation free: {} ({})", pretty_coin(&res), res); + delegated.map_or_else(show_error, |res| { + println!("Delegated: {} ({})", pretty_coin(&res), res); + }); + pledged.map_or_else(show_error, |res| { + println!("Pledged: {} ({})", pretty_coin(&res), res); + }); + staked.map_or_else(show_error, |res| { + println!("Staked: {} ({})", pretty_coin(&res), res); }); println!(); diff --git a/common/commands/src/validator/vesting/withdraw_vested.rs b/common/commands/src/validator/vesting/withdraw_vested.rs index 972c5290d9..c203bbc63d 100644 --- a/common/commands/src/validator/vesting/withdraw_vested.rs +++ b/common/commands/src/validator/vesting/withdraw_vested.rs @@ -4,7 +4,7 @@ use clap::Parser; use log::info; -use validator_client::nymd::{Coin, VestingQueryClient, VestingSigningClient}; +use validator_client::nyxd::{Coin, VestingQueryClient, VestingSigningClient}; use crate::context::SigningClient; use crate::utils::show_error; diff --git a/common/completions/Cargo.toml b/common/completions/Cargo.toml deleted file mode 100644 index ace1f76e7f..0000000000 --- a/common/completions/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "completions" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -clap = { version = "3.2", features = ["derive"] } -clap_complete = "3.2" -clap_complete_fig = "3.2" \ No newline at end of file diff --git a/common/config/Cargo.toml b/common/config/Cargo.toml index c0db6fe9e4..496d483d75 100644 --- a/common/config/Cargo.toml +++ b/common/config/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "config" +name = "nym-config" version = "0.1.0" authors = ["Jedrzej Stuczynski "] edition = "2021" @@ -9,9 +9,9 @@ edition = "2021" [dependencies] cfg-if = "1.0.0" handlebars = "3.0.1" -log = "0.4" +log = { workspace = true } serde = { version = "1.0", features = ["derive"] } toml = "0.5.6" url = "2.2" -network-defaults = { path = "../network-defaults" } +nym-network-defaults = { path = "../network-defaults" } diff --git a/common/config/src/defaults.rs b/common/config/src/defaults.rs index ffcdb0004f..1fdccc00b7 100644 --- a/common/config/src/defaults.rs +++ b/common/config/src/defaults.rs @@ -4,4 +4,4 @@ // re-export everything from the `network-defaults` to not break the existing imports // reason for moving defaults to separate crate is that I don't want to pull in all dependencies // like `handlebars`, `toml`, etc if I only want to grab one constant... -pub use network_defaults::*; +pub use nym_network_defaults::*; diff --git a/common/config/src/lib.rs b/common/config/src/lib.rs index f7bf7c2149..4d38e55467 100644 --- a/common/config/src/lib.rs +++ b/common/config/src/lib.rs @@ -2,18 +2,23 @@ // SPDX-License-Identifier: Apache-2.0 use handlebars::Handlebars; +use nym_network_defaults::mainnet::read_var_if_not_default; +use nym_network_defaults::var_names::CONFIGURED; use serde::de::DeserializeOwned; use serde::Serialize; +use std::any::type_name; +use std::fmt::Debug; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; +use std::str::FromStr; use std::{fs, io}; pub mod defaults; pub const CONFIG_DIR: &str = "config"; pub const DATA_DIR: &str = "data"; -pub const DB_FILE_NAME: &str = "db.sqlite"; +pub const CRED_DB_FILE_NAME: &str = "credentials_database.db"; pub trait NymConfig: Default + Serialize + DeserializeOwned { fn template() -> &'static str; @@ -25,23 +30,15 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned { fn default_root_directory() -> PathBuf; // default, most probable, implementations; can be easily overridden where required - fn default_config_directory(id: Option<&str>) -> PathBuf { - if let Some(id) = id { - Self::default_root_directory().join(id).join(CONFIG_DIR) - } else { - Self::default_root_directory().join(CONFIG_DIR) - } + fn default_config_directory(id: &str) -> PathBuf { + Self::default_root_directory().join(id).join(CONFIG_DIR) } - fn default_data_directory(id: Option<&str>) -> PathBuf { - if let Some(id) = id { - Self::default_root_directory().join(id).join(DATA_DIR) - } else { - Self::default_root_directory().join(DATA_DIR) - } + fn default_data_directory(id: &str) -> PathBuf { + Self::default_root_directory().join(id).join(DATA_DIR) } - fn default_config_file_path(id: Option<&str>) -> PathBuf { + fn default_config_file_path(id: &str) -> PathBuf { Self::default_config_directory(id).join(Self::config_file_name()) } @@ -49,23 +46,15 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned { fn try_default_root_directory() -> Option; - fn try_default_config_directory(id: Option<&str>) -> Option { - if let Some(id) = id { - Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR)) - } else { - Self::try_default_root_directory().map(|d| d.join(CONFIG_DIR)) - } + fn try_default_config_directory(id: &str) -> Option { + Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR)) } - fn try_default_data_directory(id: Option<&str>) -> Option { - if let Some(id) = id { - Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR)) - } else { - Self::try_default_root_directory().map(|d| d.join(DATA_DIR)) - } + fn try_default_data_directory(id: &str) -> Option { + Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR)) } - fn try_default_config_file_path(id: Option<&str>) -> Option { + fn try_default_config_file_path(id: &str) -> Option { Self::try_default_config_directory(id).map(|d| d.join(Self::config_file_name())) } @@ -108,7 +97,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned { Ok(()) } - fn load_from_file(id: Option<&str>) -> io::Result { + fn load_from_file(id: &str) -> io::Result { let file = Self::default_config_file_path(id); log::trace!("Loading from file: {:#?}", file); let config_contents = fs::read_to_string(file)?; @@ -118,13 +107,97 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned { } } -pub fn parse_validators(raw: &str) -> Vec { +// this function is only used for parsing values from the network defaults and thus the "expect" there are fine +pub fn parse_urls(raw: &str) -> Vec { raw.split(',') - .map(|raw_validator| { - raw_validator + .map(|raw_url| { + raw_url .trim() .parse() - .expect("one of the provided validator api urls is invalid") + .expect("one of the provided nym api urls is invalid") }) .collect() } + +pub trait OptionalSet { + fn with_optional(self, f: F, val: Option) -> Self + where + F: Fn(Self, T) -> Self, + Self: Sized, + { + if let Some(val) = val { + f(self, val) + } else { + self + } + } + + fn with_validated_optional( + self, + f: F, + value: Option, + validate: V, + ) -> Result + where + F: Fn(Self, T) -> Self, + V: Fn(&T) -> Result<(), E>, + Self: Sized, + { + if let Some(val) = value { + validate(&val)?; + Ok(f(self, val)) + } else { + Ok(self) + } + } + + fn with_optional_env(self, f: F, val: Option, env_var: &str) -> Self + where + F: Fn(Self, T) -> Self, + T: FromStr, + ::Err: Debug, + Self: Sized, + { + if let Some(val) = val { + return f(self, val); + } else if std::env::var(CONFIGURED).is_ok() { + if let Some(raw) = read_var_if_not_default(env_var) { + return f( + self, + raw.parse().unwrap_or_else(|err| { + panic!( + "failed to parse value of {raw} into type {}. the error was {:?}", + type_name::(), + err + ) + }), + ); + } + } + self + } + + fn with_optional_custom_env( + self, + f: F, + val: Option, + env_var: &str, + parser: G, + ) -> Self + where + F: Fn(Self, T) -> Self, + G: Fn(&str) -> T, + Self: Sized, + { + if let Some(val) = val { + return f(self, val); + } else if std::env::var(CONFIGURED).is_ok() { + if let Some(raw) = read_var_if_not_default(env_var) { + return f(self, parser(&raw)); + } + } + self + } +} + +impl OptionalSet for T where T: NymConfig {} diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml index 0d4cf1ef86..fc5e1b43d7 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "coconut-bandwidth-contract-common" +name = "nym-coconut-bandwidth-contract-common" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cosmwasm-std = "1.0.0" +cosmwasm-std = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -multisig-contract-common = { path = "../multisig-contract" } +nym-multisig-contract-common = { path = "../multisig-contract" } 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 28842576c6..ffece73fd4 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 @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_std::{from_binary, to_binary, Addr, Coin, CosmosMsg, StdResult, WasmMsg}; -use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; +use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml index cffca1ac3d..1f6d6c3f3f 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml @@ -1,15 +1,15 @@ [package] -name = "coconut-dkg-common" +name = "nym-coconut-dkg-common" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cosmwasm-std = "1.0.0" -cw-utils = "0.13.4" +cosmwasm-std = { workspace = true } +cw-utils = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -contracts-common = { path = "../contracts-common" } -multisig-contract-common = { path = "../multisig-contract" } \ No newline at end of file +contracts-common = { path = "../contracts-common", package = "nym-contracts-common" } +nym-multisig-contract-common = { path = "../multisig-contract" } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index b2d0f1ae20..62891e8fe7 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof}; +use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration}; use crate::verification_key::VerificationKeyShare; use cosmwasm_std::Addr; use schemars::JsonSchema; @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; pub struct InstantiateMsg { pub group_addr: String, pub multisig_addr: String, - pub admin: String, + pub time_configuration: Option, pub mix_denom: String, } @@ -21,20 +21,26 @@ pub enum ExecuteMsg { RegisterDealer { bte_key_with_proof: EncodedBTEPublicKeyWithProof, announce_address: String, + resharing: bool, }, CommitDealing { dealing_bytes: ContractSafeBytes, + resharing: bool, }, CommitVerificationKeyShare { share: VerificationKeyShare, + resharing: bool, }, VerifyVerificationKeyShare { owner: Addr, + resharing: bool, }, + SurpassedThreshold {}, + AdvanceEpochState {}, } @@ -43,6 +49,7 @@ pub enum ExecuteMsg { pub enum QueryMsg { GetCurrentEpochState {}, GetCurrentEpochThreshold {}, + GetInitialDealers {}, GetDealerDetails { dealer_address: String, }, @@ -60,6 +67,7 @@ pub enum QueryMsg { start_after: Option, }, GetVerificationKeys { + epoch_id: EpochId, limit: Option, start_after: Option, }, diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index ffb1653919..4eca33cb55 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -1,20 +1,148 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; +use std::str::FromStr; pub use crate::dealer::{DealerDetails, PagedDealerResponse}; pub use contracts_common::dealings::ContractSafeBytes; -pub use cosmwasm_std::{Addr, Coin}; +pub use cosmwasm_std::{Addr, Coin, Timestamp}; pub type EncodedBTEPublicKeyWithProof = String; pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str; pub type NodeIndex = u64; +pub type EpochId = u64; // 2 public attributes, 2 private attributes, 1 fixed for coconut credential pub const TOTAL_DEALINGS: usize = 2 + 2 + 1; +#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)] +pub struct InitialReplacementData { + pub initial_dealers: Vec, + pub initial_height: Option, +} + +#[derive( + Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, JsonSchema, +)] +pub struct TimeConfiguration { + // The time sign-up is open for dealers to join + pub public_key_submission_time_secs: u64, + pub dealing_exchange_time_secs: u64, + pub verification_key_submission_time_secs: u64, + pub verification_key_validation_time_secs: u64, + pub verification_key_finalization_time_secs: u64, + // The time an epoch lasts + pub in_progress_time_secs: u64, +} + +impl FromStr for TimeConfiguration { + type Err = String; + + fn from_str(s: &str) -> Result { + let times = s + .split(',') + .map(|t| t.parse()) + .collect::, _>>() + .map_err(|_| String::from("Could not parse string"))?; + if times.len() != 6 { + Err(String::from("Not enough time specified")) + } else { + Ok(TimeConfiguration { + public_key_submission_time_secs: times[0], + dealing_exchange_time_secs: times[1], + verification_key_submission_time_secs: times[2], + verification_key_validation_time_secs: times[3], + verification_key_finalization_time_secs: times[4], + in_progress_time_secs: times[5], + }) + } + } +} + +impl Default for TimeConfiguration { + fn default() -> Self { + Self { + public_key_submission_time_secs: 60 * 10, // 10 minutes + dealing_exchange_time_secs: 60 * 5, // 5 minutes + verification_key_submission_time_secs: 60 * 5, // 5 minutes + verification_key_validation_time_secs: 60, // 1 minute + verification_key_finalization_time_secs: 60, // 1 minute + in_progress_time_secs: 60 * 60 * 24 * 14, // 2 weeks + } + } +} + +#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] +#[serde(rename_all = "snake_case")] +pub struct Epoch { + pub state: EpochState, + pub epoch_id: EpochId, + pub time_configuration: TimeConfiguration, + pub finish_timestamp: Timestamp, +} + +impl Epoch { + pub fn new( + state: EpochState, + epoch_id: u64, + time_configuration: TimeConfiguration, + current_timestamp: Timestamp, + ) -> Self { + let duration = match state { + EpochState::PublicKeySubmission { .. } => { + time_configuration.public_key_submission_time_secs + } + EpochState::DealingExchange { .. } => time_configuration.dealing_exchange_time_secs, + EpochState::VerificationKeySubmission { .. } => { + time_configuration.verification_key_submission_time_secs + } + EpochState::VerificationKeyValidation { .. } => { + time_configuration.verification_key_validation_time_secs + } + EpochState::VerificationKeyFinalization { .. } => { + time_configuration.verification_key_finalization_time_secs + } + EpochState::InProgress => time_configuration.in_progress_time_secs, + }; + Epoch { + state, + epoch_id, + time_configuration, + finish_timestamp: current_timestamp.plus_seconds(duration), + } + } + + pub fn final_timestamp_secs(&self) -> u64 { + let mut finish = self.finish_timestamp.seconds(); + let time_configuration = self.time_configuration; + let mut curr_epoch_state = self.state; + while let Some(state) = curr_epoch_state.next() { + curr_epoch_state = state; + let adding = match curr_epoch_state { + EpochState::PublicKeySubmission { .. } => { + time_configuration.public_key_submission_time_secs + } + EpochState::DealingExchange { .. } => time_configuration.dealing_exchange_time_secs, + EpochState::VerificationKeySubmission { .. } => { + time_configuration.verification_key_submission_time_secs + } + EpochState::VerificationKeyValidation { .. } => { + time_configuration.verification_key_validation_time_secs + } + EpochState::VerificationKeyFinalization { .. } => { + time_configuration.verification_key_finalization_time_secs + } + EpochState::InProgress { .. } => 0, + }; + finish += adding; + } + finish + } +} + // currently (it is still extremely likely to change, we might be able to get rid of verification key-related complaints), // the epoch can be in the following states (in order): // 1. PublicKeySubmission -> potential dealers are submitting their BTE and ed25519 public keys to participate in dealing exchange @@ -30,28 +158,36 @@ pub const TOTAL_DEALINGS: usize = 2 + 2 + 1; #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] #[serde(rename_all = "snake_case")] pub enum EpochState { - PublicKeySubmission, - DealingExchange, - VerificationKeySubmission, - VerificationKeyValidation, - VerificationKeyFinalization, + PublicKeySubmission { resharing: bool }, + DealingExchange { resharing: bool }, + VerificationKeySubmission { resharing: bool }, + VerificationKeyValidation { resharing: bool }, + VerificationKeyFinalization { resharing: bool }, InProgress, } impl Default for EpochState { fn default() -> Self { - Self::PublicKeySubmission + Self::PublicKeySubmission { resharing: false } } } impl Display for EpochState { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - EpochState::PublicKeySubmission => write!(f, "PublicKeySubmission"), - EpochState::DealingExchange => write!(f, "DealingExchange"), - EpochState::VerificationKeySubmission => write!(f, "VerificationKeySubmission"), - EpochState::VerificationKeyValidation => write!(f, "VerificationKeyValidation"), - EpochState::VerificationKeyFinalization => write!(f, "VerificationKeyFinalization"), + EpochState::PublicKeySubmission { resharing } => { + write!(f, "PublicKeySubmission with resharing {resharing}") + } + EpochState::DealingExchange { resharing } => write!(f, "DealingExchange {resharing}"), + EpochState::VerificationKeySubmission { resharing } => { + write!(f, "VerificationKeySubmission with resharing {resharing}") + } + EpochState::VerificationKeyValidation { resharing } => { + write!(f, "VerificationKeyValidation with resharing {resharing}") + } + EpochState::VerificationKeyFinalization { resharing } => { + write!(f, "VerificationKeyFinalization with resharing {resharing}") + } EpochState::InProgress => write!(f, "InProgress"), } } @@ -60,11 +196,19 @@ impl Display for EpochState { impl EpochState { pub fn next(self) -> Option { match self { - EpochState::PublicKeySubmission => Some(EpochState::DealingExchange), - EpochState::DealingExchange => Some(EpochState::VerificationKeySubmission), - EpochState::VerificationKeySubmission => Some(EpochState::VerificationKeyValidation), - EpochState::VerificationKeyValidation => Some(EpochState::VerificationKeyFinalization), - EpochState::VerificationKeyFinalization => Some(EpochState::InProgress), + EpochState::PublicKeySubmission { resharing } => { + Some(EpochState::DealingExchange { resharing }) + } + EpochState::DealingExchange { resharing } => { + Some(EpochState::VerificationKeySubmission { resharing }) + } + EpochState::VerificationKeySubmission { resharing } => { + Some(EpochState::VerificationKeyValidation { resharing }) + } + EpochState::VerificationKeyValidation { resharing } => { + Some(EpochState::VerificationKeyFinalization { resharing }) + } + EpochState::VerificationKeyFinalization { .. } => Some(EpochState::InProgress), EpochState::InProgress => None, } } @@ -78,4 +222,8 @@ impl EpochState { states } + + pub fn is_final(&self) -> bool { + *self == EpochState::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 cfc3ffae03..9b8338b6f1 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::msg::ExecuteMsg; -use crate::types::NodeIndex; +use crate::types::{EpochId, NodeIndex}; use cosmwasm_std::{from_binary, to_binary, Addr, CosmosMsg, StdResult, Timestamp, WasmMsg}; use cw_utils::Expiration; -use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; +use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; use serde::{Deserialize, Serialize}; pub type VerificationKeyShare = String; @@ -16,6 +16,7 @@ pub struct ContractVKShare { pub announce_address: String, pub node_index: NodeIndex, pub owner: Addr, + pub epoch_id: EpochId, pub verified: bool, } @@ -29,11 +30,12 @@ pub struct PagedVKSharesResponse { pub fn to_cosmos_msg( owner: Addr, + resharing: bool, coconut_dkg_addr: String, multisig_addr: String, expiration_time: Timestamp, ) -> StdResult { - let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner }; + let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner, resharing }; let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: coconut_dkg_addr, msg: to_binary(&verify_vk_share_req)?, @@ -61,7 +63,8 @@ pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option { funds: _, })) = msgs.get(0) { - if let Ok(ExecuteMsg::VerifyVerificationKeyShare { owner }) = from_binary::(msg) + if let Ok(ExecuteMsg::VerifyVerificationKeyShare { owner, .. }) = + from_binary::(msg) { return Some(owner); } diff --git a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml index 351830cfe3..dbed7f9cf6 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml +++ b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml @@ -1,21 +1,18 @@ [package] -name = "contracts-common" -version = "0.1.0" -authors = ["Jędrzej Stuczyński "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +name = "nym-contracts-common" +version = "0.3.0" +description = "Common library for Nym cosmwasm contracts" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] bs58 = "0.4.0" -cosmwasm-std = "1.0.0" -dkg = { path = "../../../common/crypto/dkg", optional = true } +cosmwasm-std = { workspace = true } schemars = "0.8" serde = { version = "1.0", features = ["derive"] } thiserror = "1" [dev-dependencies] serde_json = "1.0.0" - -[features] -coconut = ["dkg"] \ No newline at end of file diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs b/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs index 98e587737e..87251d38e7 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs @@ -1,8 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "coconut")] -use dkg::{error::DkgError, Dealing}; +#[cfg(feature = "dkg")] +use nym_dkg::{error::DkgError, Dealing}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{Display, Formatter}; @@ -31,7 +31,7 @@ impl Display for ContractSafeBytes { write!(f, "0x")?; } for byte in self.0.iter().take(MAX_DISPLAY_SIZE) { - write!(f, "{:02X}", byte)?; + write!(f, "{byte:02X}")?; } // just some sanity safeguards if self.0.len() > MAX_DISPLAY_SIZE { @@ -69,19 +69,3 @@ impl<'de> Deserialize<'de> for ContractSafeBytes { Ok(ContractSafeBytes(bytes)) } } - -#[cfg(feature = "coconut")] -impl<'a> From<&'a Dealing> for ContractSafeBytes { - fn from(dealing: &'a Dealing) -> Self { - ContractSafeBytes(dealing.to_bytes()) - } -} - -#[cfg(feature = "coconut")] -impl<'a> TryFrom<&'a ContractSafeBytes> for Dealing { - type Error = DkgError; - - fn try_from(value: &'a ContractSafeBytes) -> Result { - Dealing::try_from_bytes(&value.0) - } -} diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/types.rs b/common/cosmwasm-smart-contracts/contracts-common/src/types.rs index b2721d7c6b..8ae7331d15 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/types.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/types.rs @@ -73,7 +73,7 @@ impl Percent { impl Display for Percent { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let adjusted = Decimal::from_ratio(100u32, 1u32) * self.0; - write!(f, "{}%", adjusted) + write!(f, "{adjusted}%") } } diff --git a/common/cosmwasm-smart-contracts/group-contract/Cargo.toml b/common/cosmwasm-smart-contracts/group-contract/Cargo.toml new file mode 100644 index 0000000000..9f57c84154 --- /dev/null +++ b/common/cosmwasm-smart-contracts/group-contract/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "nym-group-contract-common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cw4 = { workspace = true } +schemars = "0.8" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/common/cosmwasm-smart-contracts/group-contract/src/lib.rs b/common/cosmwasm-smart-contracts/group-contract/src/lib.rs new file mode 100644 index 0000000000..d0e87a0d30 --- /dev/null +++ b/common/cosmwasm-smart-contracts/group-contract/src/lib.rs @@ -0,0 +1 @@ +pub mod msg; diff --git a/contracts/multisig/cw4-group/src/msg.rs b/common/cosmwasm-smart-contracts/group-contract/src/msg.rs similarity index 93% rename from contracts/multisig/cw4-group/src/msg.rs rename to common/cosmwasm-smart-contracts/group-contract/src/msg.rs index d3dd63388c..a5a473135d 100644 --- a/contracts/multisig/cw4-group/src/msg.rs +++ b/common/cosmwasm-smart-contracts/group-contract/src/msg.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index 1f3e72af8a..ac6c2c2061 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -1,25 +1,26 @@ [package] -name = "mixnet-contract-common" -version = "0.1.0" -authors = ["Jędrzej Stuczyński "] -edition = "2021" +name = "nym-mixnet-contract-common" +version = "0.3.0" +description = "Common library for the Nym mixnet contract" rust-version = "1.62" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] bs58 = "0.4.0" -cosmwasm-std = "1.0.0" +cosmwasm-std = { workspace = true } serde = { version = "1.0", features = ["derive"] } serde_repr = "0.1" schemars = "0.8" thiserror = "1.0" -contracts-common = { path = "../contracts-common" } +contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.3.0" } serde_json = "1.0.0" humantime-serde = "1.1.1" # TO CHECK WHETHER STILL NEEDED: -log = "0.4.14" +log = { workspace = true } time = { version = "0.3.6", features = ["parsing", "formatting"] } ts-rs = { version = "6.1.2", optional = true } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs index 3c696a83ca..acce6bca1a 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs @@ -16,12 +16,19 @@ use serde::{Deserialize, Serialize}; pub type OwnerProxySubKey = String; pub type StorageKey = (MixId, OwnerProxySubKey); -pub fn generate_owner_storage_subkey(address: &Addr, proxy: Option<&Addr>) -> String { - if let Some(proxy) = &proxy { +// throughout the contract we ensure that our proxy can ONLY ever be the vesting contract +// thus this method is equivalent to either using the existing address (for when there's no proxy) +// or to XORing with a constant (vesting contract address) since the vesting contract address never changes. +// Thus there should be no collisions in here. +pub fn generate_owner_storage_subkey( + address: &Addr, + maybe_vesting_contract: Option<&Addr>, +) -> String { + if let Some(vesting_contract) = &maybe_vesting_contract { let key_bytes = address .as_bytes() .iter() - .zip(proxy.as_bytes()) + .zip(vesting_contract.as_bytes()) .map(|(x, y)| x ^ y) .collect::>(); bs58::encode(key_bytes).into_string() diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs index 6eff27c315..7a68f81d90 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::{IdentityKey, MixId}; +use crate::{EpochState, IdentityKey, MixId}; use cosmwasm_std::{Addr, Coin, Decimal}; use thiserror::Error; @@ -64,6 +64,19 @@ pub enum MixnetContractError { #[error("Proxy address mismatch, expected {existing}, got {incoming}")] ProxyMismatch { existing: String, incoming: String }, + #[error("Proxy address ({received}) is not set to the vesting contract ({vesting_contract})")] + ProxyIsNotVestingContract { + received: Addr, + vesting_contract: Addr, + }, + #[error( + "Sender of this message ({received}) is not the vesting contract ({vesting_contract})" + )] + SenderIsNotVestingContract { + received: Addr, + vesting_contract: Addr, + }, + #[error("Failed to recover ed25519 public key from its base58 representation - {0}")] MalformedEd25519IdentityKey(String), @@ -169,4 +182,40 @@ pub enum MixnetContractError { #[error("Feature is not yet implemented")] NotImplemented, + + #[error("epochs_in_interval must be > 0")] + EpochsInIntervalZero, + + #[error("epoch duration must be > 0")] + EpochDurationZero, + + #[error("this validator ({current_validator}) is not the one responsible for advancing this epoch. It's responsibility of {chosen_validator}.")] + RewardingValidatorMismatch { + current_validator: Addr, + chosen_validator: Addr, + }, + + #[error("the epoch is currently in the process of being advanced. (the state is {current_state}) Please try sending your transaction again once this has finished")] + EpochAdvancementInProgress { current_state: EpochState }, + + #[error("the epoch is in an unexpected state. expected 'mix rewarding' state, but we're in {current_state} instead.")] + UnexpectedNonRewardingEpochState { current_state: EpochState }, + + #[error("attempted to reward mixnode out of order. Attempted to reward {attempted_to_reward} while last rewarded was {last_rewarded}.")] + RewardingOutOfOrder { + last_rewarded: MixId, + attempted_to_reward: MixId, + }, + + #[error("the epoch is currently not in the 'event reconciliation' state. (the state is {current_state})")] + EpochNotInEventReconciliationState { current_state: EpochState }, + + #[error("the epoch is currently not in the 'epoch advancement' state. (the state is {current_state})")] + EpochNotInAdvancementState { current_state: EpochState }, + + #[error("failed to parse {value} into a valid SemVer version: {error_message}")] + SemVerFailure { + value: String, + error_message: String, + }, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs index f9f5de1802..98ecb8a1b7 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs @@ -35,6 +35,7 @@ pub enum MixnetEventType { Undelegation, ContractSettingsUpdate, RewardingValidatorUpdate, + BeginEpochTransition, AdvanceEpoch, ExecutePendingEpochEvents, ExecutePendingIntervalEvents, @@ -77,6 +78,7 @@ impl ToString for MixnetEventType { MixnetEventType::Undelegation => "undelegation", MixnetEventType::ContractSettingsUpdate => "settings_update", MixnetEventType::RewardingValidatorUpdate => "rewarding_validator_address_update", + MixnetEventType::BeginEpochTransition => "beginning_epoch_transition", MixnetEventType::AdvanceEpoch => "advance_epoch", MixnetEventType::ExecutePendingEpochEvents => "execute_pending_epoch_events", MixnetEventType::ExecutePendingIntervalEvents => "execute_pending_interval_events", @@ -86,7 +88,7 @@ impl ToString for MixnetEventType { MixnetEventType::DelegationOnUnbonding => "delegation_on_unbonding_node", }; - format!("{}{}", EVENT_VERSION_PREFIX, event_name) + format!("{EVENT_VERSION_PREFIX}{event_name}") } } @@ -139,6 +141,7 @@ pub const ZERO_PERFORMANCE_VALUE: &str = "zero_performance"; // rewarded set update pub const ACTIVE_SET_SIZE_KEY: &str = "active_set_size"; +pub const CURRENT_EPOCH_KEY: &str = "current_epoch"; pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch"; // interval @@ -500,6 +503,13 @@ pub fn new_mix_rewarding_event( ) } +pub fn new_epoch_transition_start_event(current_interval: Interval) -> Event { + Event::new(MixnetEventType::BeginEpochTransition).add_attribute( + CURRENT_EPOCH_KEY, + current_interval.current_epoch_absolute_id().to_string(), + ) +} + pub fn new_advance_epoch_event(interval: Interval, rewarded_nodes: u32) -> Event { Event::new(MixnetEventType::AdvanceEpoch) .add_attribute( diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs index b552da1931..0ab73f6085 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs @@ -56,7 +56,6 @@ impl Family { self.proxy.as_ref() } - #[allow(dead_code)] pub fn label(&self) -> &str { &self.label } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs index 5dc06ed9c3..fc0a05892b 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/helpers.rs @@ -6,9 +6,9 @@ use cosmwasm_std::{Decimal, StdError, StdResult, Uint128}; pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option) { let epsilon = epsilon.unwrap_or_else(|| Decimal::from_ratio(1u128, 100_000_000u128)); if a > b { - assert!(a - b < epsilon, "{} != {}", a, b) + assert!(a - b < epsilon, "{a} != {b}") } else { - assert!(b - a < epsilon, "{} != {}", a, b) + assert!(b - a < epsilon, "{a} != {b}") } } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs index f09a6cfe22..a7bb6caba1 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs @@ -1,9 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::error::MixnetContractError; use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent}; -use crate::{EpochId, IntervalId}; -use cosmwasm_std::Env; +use crate::{EpochId, IntervalId, MixId}; +use cosmwasm_std::{Addr, Env}; use schemars::gen::SchemaGenerator; use schemars::schema::{InstanceType, Schema, SchemaObject}; use schemars::JsonSchema; @@ -60,6 +61,133 @@ pub(crate) mod string_rfc3339_offset_date_time { } } +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct EpochStatus { + // TODO: introduce mechanism to allow another validator to take over if no progress has been made in X blocks / Y seconds + /// Specifies either, which validator is currently performing progression into the following epoch (if the epoch is currently being progressed), + /// or which validator was responsible for progressing into the current epoch (if the epoch is currently in progress) + pub being_advanced_by: Addr, + pub state: EpochState, +} + +impl EpochStatus { + pub fn new(being_advanced_by: Addr) -> Self { + EpochStatus { + being_advanced_by, + state: EpochState::InProgress, + } + } + + pub fn update_last_rewarded( + &mut self, + new_last_rewarded: MixId, + ) -> Result { + match &mut self.state { + EpochState::Rewarding { + ref mut last_rewarded, + final_node_id, + } => { + if new_last_rewarded <= *last_rewarded { + return Err(MixnetContractError::RewardingOutOfOrder { + last_rewarded: *last_rewarded, + attempted_to_reward: new_last_rewarded, + }); + } + + *last_rewarded = new_last_rewarded; + Ok(new_last_rewarded == *final_node_id) + } + state => Err(MixnetContractError::UnexpectedNonRewardingEpochState { + current_state: *state, + }), + } + } + + pub fn last_rewarded(&self) -> Result { + match self.state { + EpochState::Rewarding { last_rewarded, .. } => Ok(last_rewarded), + state => Err(MixnetContractError::UnexpectedNonRewardingEpochState { + current_state: state, + }), + } + } + + pub fn ensure_is_in_event_reconciliation_state(&self) -> Result<(), MixnetContractError> { + if !matches!(self.state, EpochState::ReconcilingEvents) { + return Err(MixnetContractError::EpochNotInEventReconciliationState { + current_state: self.state, + }); + } + Ok(()) + } + + pub fn ensure_is_in_advancement_state(&self) -> Result<(), MixnetContractError> { + if !matches!(self.state, EpochState::AdvancingEpoch) { + return Err(MixnetContractError::EpochNotInAdvancementState { + current_state: self.state, + }); + } + Ok(()) + } + + pub fn is_in_progress(&self) -> bool { + matches!(self.state, EpochState::InProgress) + } + + pub fn is_rewarding(&self) -> bool { + matches!(self.state, EpochState::Rewarding { .. }) + } + + pub fn is_reconciling(&self) -> bool { + matches!(self.state, EpochState::ReconcilingEvents) + } + + pub fn is_advancing(&self) -> bool { + matches!(self.state, EpochState::AdvancingEpoch) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)] +pub enum EpochState { + /// Represents the state of an epoch that's in progress (well, duh.) + /// All actions are allowed to be issued. + InProgress, + + /// Represents the state of an epoch when the rewarding entity has been decided on, + /// and the mixnodes are in the process of being rewarded for their work in this epoch. + Rewarding { + last_rewarded: MixId, + + final_node_id: MixId, + // total_rewarded: u32, + }, + + /// Represents the state of an epoch when all mixnodes have already been rewarded for their work in this epoch + /// and all issued actions should now get resolved before being allowed to advance into the next epoch. + ReconcilingEvents, + + /// Represents the state of an epoch when all mixnodes have already been rewarded for their work in this epoch, + /// all issued actions got resolved and the epoch should now be advanced whilst assigning new rewarded set. + AdvancingEpoch, +} + +impl Display for EpochState { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + EpochState::InProgress => write!(f, "in progress"), + EpochState::Rewarding { + last_rewarded, + final_node_id, + } => write!( + f, + "mix rewarding (last rewarded: {last_rewarded}, final node: {final_node_id})" + ), + EpochState::ReconcilingEvents => write!(f, "event reconciliation"), + EpochState::AdvancingEpoch => write!(f, "advancing epoch"), + } + } +} + #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( feature = "generate-ts", @@ -400,6 +528,21 @@ impl PendingIntervalEventsResponse { } } +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct NumberOfPendingEventsResponse { + pub epoch_events: u32, + pub interval_events: u32, +} + +impl NumberOfPendingEventsResponse { + pub fn new(epoch_events: u32, interval_events: u32) -> Self { + Self { + epoch_events, + interval_events, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs index adabf7de30..4adc351471 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 #![warn(clippy::expect_used)] @@ -29,7 +29,8 @@ pub use gateway::{ Gateway, GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, PagedGatewayResponse, }; pub use interval::{ - CurrentIntervalResponse, Interval, PendingEpochEventsResponse, PendingIntervalEventsResponse, + CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse, + PendingEpochEventsResponse, PendingIntervalEventsResponse, }; pub use mixnode::{ Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index cc86757367..1af718afde 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -101,15 +101,16 @@ pub enum ExecuteMsg { owner_signature: String, label: String, }, - /// Family head needs to sign the joining node IdentityKey + /// Family head needs to sign the joining node IdentityKey, MixNode needs to provide its signature proving that it wants to join the family JoinFamilyOnBehalf { member_address: String, - signature: String, + node_identity_signature: String, + family_signature: String, family_head: IdentityKey, }, LeaveFamilyOnBehalf { member_address: String, - signature: String, + node_identity_signature: String, family_head: IdentityKey, }, KickFamilyMemberOnBehalf { @@ -138,6 +139,7 @@ pub enum ExecuteMsg { epoch_duration_secs: u64, force_immediately: bool, }, + BeginEpochTransition {}, AdvanceCurrentEpoch { new_rewarded_set: Vec, // families_in_layer: HashMap, @@ -241,30 +243,30 @@ impl ExecuteMsg { pub fn default_memo(&self) -> String { match self { ExecuteMsg::AssignNodeLayer { mix_id, layer } => { - format!("assigning mix {} for layer {:?}", mix_id, layer) + format!("assigning mix {mix_id} for layer {layer:?}") } ExecuteMsg::CreateFamily { .. } => "crating node family with".to_string(), ExecuteMsg::JoinFamily { family_head, .. } => { - format!("joining family {}", family_head) + format!("joining family {family_head}") } ExecuteMsg::LeaveFamily { family_head, .. } => { - format!("leaving family {}", family_head) + format!("leaving family {family_head}") } ExecuteMsg::KickFamilyMember { member, .. } => { - format!("kicking {} from family", member) + format!("kicking {member} from family") } ExecuteMsg::CreateFamilyOnBehalf { .. } => "crating node family with".to_string(), ExecuteMsg::JoinFamilyOnBehalf { family_head, .. } => { - format!("joining family {}", family_head) + format!("joining family {family_head}") } ExecuteMsg::LeaveFamilyOnBehalf { family_head, .. } => { - format!("leaving family {}", family_head) + format!("leaving family {family_head}") } ExecuteMsg::KickFamilyMemberOnBehalf { member, .. } => { - format!("kicking {} from family", member) + format!("kicking {member} from family") } ExecuteMsg::UpdateRewardingValidatorAddress { address } => { - format!("updating rewarding validator to {}", address) + format!("updating rewarding validator to {address}") } ExecuteMsg::UpdateContractStateParams { .. } => { "updating mixnet state parameters".into() @@ -273,21 +275,15 @@ impl ExecuteMsg { active_set_size, force_immediately, } => format!( - "updating active set size to {}. forced: {}", - active_set_size, force_immediately + "updating active set size to {active_set_size}. forced: {force_immediately}" ), ExecuteMsg::UpdateRewardingParams { force_immediately, .. - } => format!( - "updating mixnet rewarding parameters. forced: {}", - force_immediately - ), + } => format!("updating mixnet rewarding parameters. forced: {force_immediately}"), ExecuteMsg::UpdateIntervalConfig { force_immediately, .. - } => format!( - "updating mixnet interval configuration. forced: {}", - force_immediately - ), + } => format!("updating mixnet interval configuration. forced: {force_immediately}"), + ExecuteMsg::BeginEpochTransition {} => "beginning epoch transition".into(), ExecuteMsg::AdvanceCurrentEpoch { .. } => "advancing current epoch".into(), ExecuteMsg::ReconcileEpochEvents { .. } => "reconciling epoch events".into(), ExecuteMsg::BondMixnode { mix_node, .. } => { @@ -316,34 +312,30 @@ impl ExecuteMsg { } ExecuteMsg::UnbondGateway { .. } => "unbonding gateway".into(), ExecuteMsg::UnbondGatewayOnBehalf { .. } => "unbonding gateway on behalf".into(), - ExecuteMsg::DelegateToMixnode { mix_id } => format!("delegating to mixnode {}", mix_id), + ExecuteMsg::DelegateToMixnode { mix_id } => format!("delegating to mixnode {mix_id}"), ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, .. } => { - format!("delegating to mixnode {} on behalf", mix_id) + format!("delegating to mixnode {mix_id} on behalf") } ExecuteMsg::UndelegateFromMixnode { mix_id } => { - format!("removing delegation from mixnode {}", mix_id) + format!("removing delegation from mixnode {mix_id}") } ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, .. } => { - format!("removing delegation from mixnode {} on behalf", mix_id) + format!("removing delegation from mixnode {mix_id} on behalf") } ExecuteMsg::RewardMixnode { mix_id, performance, - } => format!( - "rewarding mixnode {} for performance {}", - mix_id, performance - ), + } => format!("rewarding mixnode {mix_id} for performance {performance}"), ExecuteMsg::WithdrawOperatorReward { .. } => "withdrawing operator reward".into(), ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. } => { "withdrawing operator reward on behalf".into() } ExecuteMsg::WithdrawDelegatorReward { mix_id } => { - format!("withdrawing delegator reward from mixnode {}", mix_id) + format!("withdrawing delegator reward from mixnode {mix_id}") + } + ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, .. } => { + format!("withdrawing delegator reward from mixnode {mix_id} on behalf") } - ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, .. } => format!( - "withdrawing delegator reward from mixnode {} on behalf", - mix_id - ), #[cfg(feature = "contract-testing")] ExecuteMsg::TestingResolveAllPendingEvents { .. } => { "resolving all pending events".into() @@ -378,10 +370,13 @@ pub enum QueryMsg { }, // state/sys-params-related GetContractVersion {}, + #[serde(rename = "get_cw2_contract_version")] + GetCW2ContractVersion {}, GetRewardingValidatorAddress {}, GetStateParams {}, GetState {}, GetRewardingParams {}, + GetEpochStatus {}, GetCurrentIntervalDetails {}, GetRewardedSet { limit: Option, @@ -504,6 +499,7 @@ pub enum QueryMsg { limit: Option, start_after: Option, }, + GetNumberOfPendingEvents {}, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] diff --git a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml index f493cb6635..14ef586c93 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml @@ -1,15 +1,16 @@ [package] -name = "multisig-contract-common" +name = "nym-multisig-contract-common" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cw-utils = { version = "0.13.4" } -cw3 = { version = "0.13.4" } -cw3-fixed-multisig = { version = "0.13.4", features = ["library"] } -cw4 = { version = "0.13.4" } -cosmwasm-std = "1.0.0" +cw-utils = { workspace = true } +cw3 = { workspace = true } +cw3-fixed-multisig = { workspace = true, features = ["library"] } +cw4 = { workspace= true } +cosmwasm-std = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.23" } diff --git a/contracts/multisig/cw3-flex-multisig/src/error.rs b/common/cosmwasm-smart-contracts/multisig-contract/src/error.rs similarity index 86% rename from contracts/multisig/cw3-flex-multisig/src/error.rs rename to common/cosmwasm-smart-contracts/multisig-contract/src/error.rs index f7addae37b..b10fa6b506 100644 --- a/contracts/multisig/cw3-flex-multisig/src/error.rs +++ b/common/cosmwasm-smart-contracts/multisig-contract/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use cosmwasm_std::StdError; use cw_utils::ThresholdError; @@ -5,10 +8,10 @@ use thiserror::Error; #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("{0}")] + #[error(transparent)] Std(#[from] StdError), - #[error("{0}")] + #[error(transparent)] Threshold(#[from] ThresholdError), #[error("Group contract invalid address '{addr}'")] diff --git a/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs b/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs index d0e87a0d30..a968ed14e5 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs @@ -1 +1,2 @@ +pub mod error; pub mod msg; diff --git a/common/cosmwasm-smart-contracts/vesting-contract/.DS_Store b/common/cosmwasm-smart-contracts/vesting-contract/.DS_Store deleted file mode 100644 index 5172429f26..0000000000 Binary files a/common/cosmwasm-smart-contracts/vesting-contract/.DS_Store and /dev/null differ diff --git a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml index c4a51aff9d..88a67765d9 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml @@ -1,12 +1,16 @@ [package] -name = "vesting-contract-common" -version = "0.1.0" -edition = "2021" +name = "nym-vesting-contract-common" +version = "0.3.0" +description = "Common library for the Nym vesting contract" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] -cosmwasm-std = "1.0.0" -mixnet-contract-common = { path = "../mixnet-contract" } -contracts-common = { path = "../contracts-common" } +cosmwasm-std = { workspace = true } +mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.3.0" } +contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.3.0" } serde = { version = "1.0", features = ["derive"] } schemars = "0.8" ts-rs = {version = "6.1.2", optional = true} diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs index 0153ec9ad7..75d916ddad 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs @@ -64,7 +64,7 @@ impl FromStr for PledgeCap { Ok(p) => Ok(PledgeCap::Percent(p)), Err(_) => match cap.parse::() { Ok(i) => Ok(PledgeCap::Absolute(Uint128::from(i))), - Err(_e) => Err(format!("Could not parse {} as Percent or Uint128", cap)), + Err(_e) => Err(format!("Could not parse {cap} as Percent or Uint128")), }, } } @@ -134,6 +134,32 @@ pub struct AllDelegationsResponse { pub start_next_after: Option<(u32, MixId, u64)>, } +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +pub struct AccountVestingCoins { + pub account_id: u32, + pub owner: Addr, + pub still_vesting: Coin, +} + +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +pub struct VestingCoinsResponse { + pub accounts: Vec, + pub start_next_after: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +pub struct BaseVestingAccountInfo { + pub account_id: u32, + pub owner: Addr, + // TODO: should this particular query/response expose anything else? +} + +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +pub struct AccountsResponse { + pub accounts: Vec, + pub start_next_after: Option, +} + #[cfg(test)] mod test { use contracts_common::Percent; diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs index 3edc6c2303..8df999f2a6 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs @@ -17,9 +17,7 @@ pub struct InitMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub struct MigrateMsg { - pub v2_mixnet_contract_address: String, -} +pub struct MigrateMsg {} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)] pub struct VestingSpecification { @@ -63,13 +61,14 @@ pub enum ExecuteMsg { owner_signature: String, label: String, }, - /// Family head needs to sign the joining node IdentityKey + /// Family head needs to sign the joining node IdentityKey, the Node provides its signature signaling consent to join the family JoinFamily { - signature: String, + node_identity_signature: String, + family_signature: String, family_head: IdentityKey, }, LeaveFamily { - signature: String, + node_identity_signature: String, family_head: IdentityKey, }, KickFamilyMember { @@ -96,9 +95,11 @@ pub enum ExecuteMsg { DelegateToMixnode { mix_id: MixId, amount: Coin, + on_behalf_of: Option, }, UndelegateFromMixnode { mix_id: MixId, + on_behalf_of: Option, }, CreateAccount { owner_address: String, @@ -188,6 +189,16 @@ impl ExecuteMsg { #[serde(rename_all = "snake_case")] pub enum QueryMsg { GetContractVersion {}, + #[serde(rename = "get_cw2_contract_version")] + GetCW2ContractVersion {}, + GetAccountsPaged { + start_next_after: Option, + limit: Option, + }, + GetAccountsVestingCoinsPaged { + start_next_after: Option, + limit: Option, + }, LockedCoins { vesting_account_address: String, block_time: Option, @@ -213,12 +224,25 @@ pub enum QueryMsg { GetOriginalVesting { vesting_account_address: String, }, - GetDelegatedFree { - block_time: Option, + GetHistoricalVestingStakingReward { vesting_account_address: String, }, - GetDelegatedVesting { - block_time: Option, + GetSpendableVestedCoins { + vesting_account_address: String, + }, + GetSpendableRewardCoins { + vesting_account_address: String, + }, + GetDelegatedCoins { + vesting_account_address: String, + }, + GetPledgedCoins { + vesting_account_address: String, + }, + GetStakedCoins { + vesting_account_address: String, + }, + GetWithdrawnCoins { vesting_account_address: String, }, GetAccount { diff --git a/common/credential-storage/Cargo.toml b/common/credential-storage/Cargo.toml index 4eacccd531..c488eab99b 100644 --- a/common/credential-storage/Cargo.toml +++ b/common/credential-storage/Cargo.toml @@ -1,19 +1,19 @@ [package] -name = "credential-storage" +name = "nym-credential-storage" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait = { version = "0.1.51" } +async-trait = { workspace = true } -log = "0.4" +log = { workspace = true } sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]} thiserror = "1.0" -tokio = { version = "1.21.2", features = [ "rt-multi-thread", "net", "signal", "fs" ] } +tokio = { version = "1.24.1", features = [ "rt-multi-thread", "net", "signal", "fs" ] } [build-dependencies] sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } -tokio = { version = "1.21.2", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } diff --git a/common/credential-storage/build.rs b/common/credential-storage/build.rs index ad3cfe4bff..4716c65d0d 100644 --- a/common/credential-storage/build.rs +++ b/common/credential-storage/build.rs @@ -9,9 +9,9 @@ use std::env; #[tokio::main] async fn main() { let out_dir = env::var("OUT_DIR").unwrap(); - let database_path = format!("{}/coconut-credential-example.sqlite", out_dir); + let database_path = format!("{out_dir}/coconut-credential-example.sqlite"); - let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path)) + let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc")) .await .expect("Failed to create SQLx database connection"); diff --git a/common/credential-storage/migrations/20230117120000_add_epoch_id_to_coconut.sql b/common/credential-storage/migrations/20230117120000_add_epoch_id_to_coconut.sql new file mode 100644 index 0000000000..2ecd81bcee --- /dev/null +++ b/common/credential-storage/migrations/20230117120000_add_epoch_id_to_coconut.sql @@ -0,0 +1,17 @@ +/* + * Copyright 2023 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +DROP TABLE coconut_credentials; +CREATE TABLE coconut_credentials +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + voucher_value TEXT NOT NULL, + voucher_info TEXT NOT NULL, + serial_number TEXT NOT NULL, + binding_number TEXT NOT NULL, + signature TEXT NOT NULL UNIQUE, + epoch_id TEXT NOT NULL, + consumed BOOLEAN NOT NULL +); \ No newline at end of file diff --git a/common/credential-storage/src/coconut.rs b/common/credential-storage/src/coconut.rs index 103f8760d2..a6cd968c2e 100644 --- a/common/credential-storage/src/coconut.rs +++ b/common/credential-storage/src/coconut.rs @@ -34,10 +34,11 @@ impl CoconutCredentialManager { serial_number: String, binding_number: String, signature: String, + epoch_id: String, ) -> Result<(), sqlx::Error> { sqlx::query!( - "INSERT INTO coconut_credentials(voucher_value, voucher_info, serial_number, binding_number, signature, consumed) VALUES (?, ?, ?, ?, ?, ?)", - voucher_value, voucher_info, serial_number, binding_number, signature, false + "INSERT INTO coconut_credentials(voucher_value, voucher_info, serial_number, binding_number, signature, epoch_id, consumed) VALUES (?, ?, ?, ?, ?, ?, ?)", + voucher_value, voucher_info, serial_number, binding_number, signature, epoch_id, false ) .execute(&self.connection_pool) .await?; diff --git a/common/credential-storage/src/lib.rs b/common/credential-storage/src/lib.rs index 3b683026ce..bf3af1b272 100644 --- a/common/credential-storage/src/lib.rs +++ b/common/credential-storage/src/lib.rs @@ -45,13 +45,13 @@ impl PersistentStorage { let connection_pool = match sqlx::SqlitePool::connect_with(opts).await { Ok(db) => db, Err(err) => { - error!("Failed to connect to SQLx database: {}", err); + error!("Failed to connect to SQLx database: {err}"); return Err(err.into()); } }; if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await { - error!("Failed to perform migration on the SQLx database: {}", err); + error!("Failed to perform migration on the SQLx database: {err}"); return Err(err.into()); } @@ -70,6 +70,7 @@ impl Storage for PersistentStorage { serial_number: String, binding_number: String, signature: String, + epoch_id: String, ) -> Result<(), StorageError> { self.coconut_credential_manager .insert_coconut_credential( @@ -78,6 +79,7 @@ impl Storage for PersistentStorage { serial_number, binding_number, signature, + epoch_id, ) .await?; @@ -104,7 +106,7 @@ impl Storage for PersistentStorage { pub async fn initialise_storage(path: PathBuf) -> PersistentStorage { match PersistentStorage::init(path).await { - Err(err) => panic!("failed to initialise credential storage - {}", err), + Err(err) => panic!("failed to initialise credential storage - {err}"), Ok(storage) => storage, } } diff --git a/common/credential-storage/src/models.rs b/common/credential-storage/src/models.rs index 6ce7a9dd4b..67f6e4790c 100644 --- a/common/credential-storage/src/models.rs +++ b/common/credential-storage/src/models.rs @@ -9,5 +9,6 @@ pub struct CoconutCredential { pub serial_number: String, pub binding_number: String, pub signature: String, + pub epoch_id: String, pub consumed: bool, } diff --git a/common/credential-storage/src/storage.rs b/common/credential-storage/src/storage.rs index 77df96f4c8..bf3285cae6 100644 --- a/common/credential-storage/src/storage.rs +++ b/common/credential-storage/src/storage.rs @@ -12,7 +12,12 @@ pub trait Storage: Send + Sync { /// /// # Arguments /// + /// * `voucher_value`: How much bandwidth is in the credential. + /// * `voucher_info`: What type of credential it is. + /// * `serial_number`: Serial number of the credential. + /// * `binding_number`: Binding number of the credential. /// * `signature`: Coconut credential in the form of a signature. + /// * `epoch_id`: The epoch when it was signed. async fn insert_coconut_credential( &self, voucher_value: String, @@ -20,6 +25,7 @@ pub trait Storage: Send + Sync { serial_number: String, binding_number: String, signature: String, + epoch_id: String, ) -> Result<(), StorageError>; /// Tries to retrieve one of the stored, unused credentials. diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index 58d43af03f..c7c64c4301 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "credentials" +name = "nym-credentials" version = "0.1.0" edition = "2021" @@ -7,17 +7,15 @@ edition = "2021" [dependencies] bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] } -cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", optional = true } +cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } thiserror = "1.0" # I guess temporarily until we get serde support in coconut up and running -coconut-interface = { path = "../coconut-interface" } -crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "hashing"] } -validator-api-requests = { path = "../../validator-api/validator-api-requests" } +nym-coconut-interface = { path = "../coconut-interface" } +nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "hashing"] } +nym-api-requests = { path = "../../nym-api/nym-api-requests" } validator-client = { path = "../client-libs/validator-client" } [dev-dependencies] rand = "0.7.3" -[features] -coconut = ["cosmrs"] diff --git a/common/credentials/src/coconut/bandwidth.rs b/common/credentials/src/coconut/bandwidth.rs index ed38fbb14c..9c87e75341 100644 --- a/common/credentials/src/coconut/bandwidth.rs +++ b/common/credentials/src/coconut/bandwidth.rs @@ -6,11 +6,11 @@ // right now this has no double-spending protection, spender binding, etc // it's the simplest possible case -use coconut_interface::{ +use nym_coconut_interface::{ hash_to_scalar, prepare_blind_sign, Attribute, BlindSignRequest, Credential, Parameters, PrivateAttribute, PublicAttribute, Signature, VerificationKey, }; -use crypto::asymmetric::{encryption, identity}; +use nym_crypto::asymmetric::{encryption, identity}; use cosmrs::tx::Hash; @@ -42,41 +42,9 @@ pub struct BandwidthVoucher { encryption_key: encryption::PrivateKey, pedersen_commitments_openings: Vec, blind_sign_request: BlindSignRequest, - use_request: bool, } impl BandwidthVoucher { - pub fn new_with_blind_sign_req( - private_attributes: [PrivateAttribute; PRIVATE_ATTRIBUTES as usize], - public_attributes_plain: [&str; PUBLIC_ATTRIBUTES as usize], - tx_hash: Hash, - signing_key: identity::PrivateKey, - encryption_key: encryption::PrivateKey, - pedersen_commitments_openings: Vec, - blind_sign_request: BlindSignRequest, - ) -> Self { - let voucher_value = public_attributes_plain[0]; - let voucher_info = public_attributes_plain[1]; - let voucher_value_plain = voucher_value.to_string(); - let voucher_info_plain = voucher_info.to_string(); - let voucher_value = hash_to_scalar(voucher_value.as_bytes()); - let voucher_info = hash_to_scalar(voucher_info.as_bytes()); - - BandwidthVoucher { - serial_number: private_attributes[0], - binding_number: private_attributes[1], - voucher_value, - voucher_value_plain, - voucher_info, - voucher_info_plain, - tx_hash, - signing_key, - encryption_key, - pedersen_commitments_openings, - blind_sign_request, - use_request: false, - } - } pub fn new( params: &Parameters, voucher_value: String, @@ -109,10 +77,135 @@ impl BandwidthVoucher { encryption_key, pedersen_commitments_openings, blind_sign_request, - use_request: true, } } + pub fn to_bytes(&self) -> Vec { + let serial_number_b = self.serial_number.to_bytes(); + let binding_number_b = self.binding_number.to_bytes(); + let voucher_value_plain_b = self.voucher_value_plain.as_bytes(); + 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 blind_sign_request_b = self.blind_sign_request.to_bytes(); + + let mut ret = Vec::new(); + + ret.extend_from_slice(&serial_number_b); + ret.extend_from_slice(&binding_number_b); + ret.extend_from_slice(tx_hash_b); + ret.extend_from_slice(&signing_key_b); + ret.extend_from_slice(&encryption_key_b); + ret.extend_from_slice(&(voucher_value_plain_b.len() as u64).to_be_bytes()); + ret.extend_from_slice(&(voucher_info_plain_b.len() as u64).to_be_bytes()); + ret.extend_from_slice(&(blind_sign_request_b.len() as u64).to_be_bytes()); + ret.extend_from_slice(&(self.pedersen_commitments_openings.len() as u64).to_be_bytes()); + ret.extend_from_slice(voucher_value_plain_b); + ret.extend_from_slice(voucher_info_plain_b); + ret.extend_from_slice(&blind_sign_request_b); + for commitment in self.pedersen_commitments_openings.iter() { + ret.extend_from_slice(&commitment.to_bytes()); + } + + ret + } + + pub fn try_from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < 32 * 5 + 4 * 8 { + return Err(Error::BandwidthVoucherDeserializationError(format!( + "Less then {} bytes needed", + 32 * 5 + 4 * 8 + ))); + } + let mut buff = [0u8; 32]; + let mut small_buff = [0u8; 8]; + let scalar_err = + || Error::BandwidthVoucherDeserializationError(String::from("Invalid Scalar")); + buff.copy_from_slice(&bytes[..32]); + let serial_number = Option::::from(PrivateAttribute::from_bytes(&buff)) + .ok_or_else(scalar_err)?; + buff.copy_from_slice(&bytes[32..2 * 32]); + let binding_number = Option::::from(PrivateAttribute::from_bytes(&buff)) + .ok_or_else(scalar_err)?; + buff.copy_from_slice(&bytes[2 * 32..3 * 32]); + let tx_hash = Hash::new(buff); + buff.copy_from_slice(&bytes[3 * 32..4 * 32]); + let signing_key = identity::PrivateKey::from_bytes(&buff).map_err(|_| { + Error::BandwidthVoucherDeserializationError(String::from("Invalid key")) + })?; + buff.copy_from_slice(&bytes[4 * 32..5 * 32]); + let encryption_key = encryption::PrivateKey::from_bytes(&buff).map_err(|_| { + Error::BandwidthVoucherDeserializationError(String::from("Invalid key")) + })?; + small_buff.copy_from_slice(&bytes[5 * 32..5 * 32 + 8]); + let voucher_value_plain_no = u64::from_be_bytes(small_buff) as usize; + small_buff.copy_from_slice(&bytes[5 * 32 + 8..5 * 32 + 2 * 8]); + let voucher_info_plain_no = u64::from_be_bytes(small_buff) as usize; + small_buff.copy_from_slice(&bytes[5 * 32 + 2 * 8..5 * 32 + 3 * 8]); + let blind_sign_request_no = u64::from_be_bytes(small_buff) as usize; + small_buff.copy_from_slice(&bytes[5 * 32 + 3 * 8..5 * 32 + 4 * 8]); + let pedersen_commitments_openings_no = u64::from_be_bytes(small_buff) as usize; + + let total_length = 32 * 5 + + 4 * 8 + + voucher_value_plain_no + + voucher_info_plain_no + + blind_sign_request_no + + pedersen_commitments_openings_no * 32; + if bytes.len() != total_length { + return Err(Error::BandwidthVoucherDeserializationError(format!( + "Expected {total_length} bytes", + ))); + } + + let utf_err = |_| { + Err(Error::BandwidthVoucherDeserializationError(String::from( + "Invalid UTF8 string", + ))) + }; + let mut var_length_pointer = 5 * 32 + 4 * 8; + let voucher_value_plain = String::from_utf8( + 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); + 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); + 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], + )?; + var_length_pointer += blind_sign_request_no; + + let mut pedersen_commitments_openings = Vec::new(); + for _ in 0..pedersen_commitments_openings_no { + buff.copy_from_slice(&bytes[var_length_pointer..var_length_pointer + 32]); + let commitment = + Option::::from(Attribute::from_bytes(&buff)).ok_or_else(scalar_err)?; + var_length_pointer += 32; + pedersen_commitments_openings.push(commitment); + } + + Ok(Self { + serial_number, + binding_number, + voucher_value, + voucher_value_plain, + voucher_info, + voucher_info_plain, + tx_hash, + signing_key, + 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 { values.len() == 2 @@ -141,8 +234,8 @@ impl BandwidthVoucher { &self.blind_sign_request } - pub fn use_request(&self) -> bool { - self.use_request + pub fn get_voucher_value(&self) -> String { + self.voucher_value_plain.clone() } pub fn get_public_attributes_plain(&self) -> Vec { @@ -168,6 +261,7 @@ pub fn prepare_for_spending( voucher_info: String, serial_number: PrivateAttribute, binding_number: PrivateAttribute, + epoch_id: u64, signature: &Signature, verification_key: &VerificationKey, ) -> Result { @@ -179,6 +273,7 @@ pub fn prepare_for_spending( voucher_info, serial_number, binding_number, + epoch_id, signature, verification_key, ) @@ -187,13 +282,13 @@ pub fn prepare_for_spending( #[cfg(test)] mod test { use super::*; + use nym_coconut_interface::Base58; use rand::rngs::OsRng; - #[test] - fn voucher_consistency() { + fn voucher_fixture() -> BandwidthVoucher { let params = Parameters::new(4).unwrap(); let mut rng = OsRng; - let voucher = BandwidthVoucher::new( + BandwidthVoucher::new( ¶ms, "1234".to_string(), "voucher info".to_string(), @@ -208,7 +303,48 @@ mod test { &encryption::KeyPair::new(&mut rng).private_key().to_bytes(), ) .unwrap(), + ) + } + + #[test] + fn serde_voucher() { + let voucher = voucher_fixture(); + let bytes = voucher.to_bytes(); + 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.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() + ); + assert_eq!( + voucher.pedersen_commitments_openings, + deserialized_voucher.pedersen_commitments_openings + ); + assert_eq!( + voucher.blind_sign_request.to_bs58(), + deserialized_voucher.blind_sign_request.to_bs58() + ); + } + + #[test] + fn voucher_consistency() { + let voucher = voucher_fixture(); assert!(!BandwidthVoucher::verify_against_plain( &[], &voucher.get_public_attributes_plain() diff --git a/common/credentials/src/coconut/params.rs b/common/credentials/src/coconut/params.rs index 42b9205ebb..906ef7cae0 100644 --- a/common/credentials/src/coconut/params.rs +++ b/common/credentials/src/coconut/params.rs @@ -1,15 +1,15 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crypto::aes::Aes128; -use crypto::blake3; -use crypto::ctr; +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 ValidatorApiCredentialHkdfAlgorithm = blake3::Hasher; +pub type NymApiCredentialHkdfAlgorithm = blake3::Hasher; /// Encryption algorithm used for end-to-end encryption of blinded signature response -pub type ValidatorApiCredentialEncryptionAlgorithm = Aes128Ctr; +pub type NymApiCredentialEncryptionAlgorithm = Aes128Ctr; diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index f701938d59..5a912705ce 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -1,20 +1,18 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_interface::{ +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, }; -use crypto::asymmetric::encryption::PublicKey; -use crypto::shared_key::recompute_shared_key; -use crypto::symmetric::stream_cipher; -use validator_api_requests::coconut::BlindSignRequestBody; +use nym_crypto::asymmetric::encryption::PublicKey; +use nym_crypto::shared_key::recompute_shared_key; +use nym_crypto::symmetric::stream_cipher; use validator_client::client::CoconutApiClient; use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; -use crate::coconut::params::{ - ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm, -}; +use crate::coconut::params::{NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm}; use crate::error::Error; pub async fn obtain_aggregate_verification_key( @@ -39,7 +37,7 @@ pub async fn obtain_aggregate_verification_key( async fn obtain_partial_credential( params: &Parameters, attributes: &BandwidthVoucher, - client: &validator_client::client::ApiClient, + client: &validator_client::client::NymApiClient, validator_vk: &VerificationKey, ) -> Result { let public_attributes = attributes.get_public_attributes(); @@ -47,30 +45,24 @@ async fn obtain_partial_credential( let private_attributes = attributes.get_private_attributes(); let blind_sign_request = attributes.blind_sign_request(); - let response = if attributes.use_request() { - 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, - public_attributes_plain, - (public_attributes.len() + private_attributes.len()) as u32, - ); - client.blind_sign(&blind_sign_request_body).await? - } else { - client - .partial_bandwidth_credential(&attributes.tx_hash().to_string()) - .await? - }; + 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, + 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::< - ValidatorApiCredentialEncryptionAlgorithm, - ValidatorApiCredentialHkdfAlgorithm, + NymApiCredentialEncryptionAlgorithm, + NymApiCredentialHkdfAlgorithm, >(&remote_key, attributes.encryption_key()); - let zero_iv = stream_cipher::zero_iv::(); - let blinded_signature_bytes = stream_cipher::decrypt::( + let zero_iv = stream_cipher::zero_iv::(); + let blinded_signature_bytes = stream_cipher::decrypt::( &encryption_key, &zero_iv, &encrypted_signature, @@ -94,6 +86,7 @@ pub async fn obtain_aggregate_signature( params: &Parameters, attributes: &BandwidthVoucher, coconut_api_clients: &[CoconutApiClient], + threshold: u64, ) -> Result { if coconut_api_clients.is_empty() { return Err(Error::NoValidatorsAvailable); @@ -110,41 +103,43 @@ pub async fn obtain_aggregate_signature( .iter() .map(|api_client| api_client.node_id) .collect(); + let verification_key = + aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?; for coconut_api_client in coconut_api_clients.iter() { - let signature = obtain_partial_credential( + if let Ok(signature) = obtain_partial_credential( params, attributes, &coconut_api_client.api_client, &coconut_api_client.verification_key, ) - .await?; - let share = SignatureShare::new(signature, coconut_api_client.node_id); - shares.push(share) + .await + { + let share = SignatureShare::new(signature, coconut_api_client.node_id); + shares.push(share) + } + } + if shares.len() < threshold as usize { + return Err(Error::NotEnoughShares); } let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len()); attributes.extend_from_slice(&private_attributes); attributes.extend_from_slice(&public_attributes); - let verification_key = - aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?; - - Ok(aggregate_signature_shares( - params, - &verification_key, - &attributes, - &shares, - )?) + aggregate_signature_shares(params, &verification_key, &attributes, &shares) + .map_err(Error::SignatureAggregationError) } // TODO: better type flow +#[allow(clippy::too_many_arguments)] pub fn prepare_credential_for_spending( params: &Parameters, voucher_value: u64, voucher_info: String, serial_number: Attribute, binding_number: Attribute, + epoch_id: u64, signature: &Signature, verification_key: &VerificationKey, ) -> Result { @@ -161,5 +156,6 @@ pub fn prepare_credential_for_spending( theta, voucher_value, voucher_info, + epoch_id, )) } diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs index 86b9abc140..e35cca27e4 100644 --- a/common/credentials/src/error.rs +++ b/common/credentials/src/error.rs @@ -1,22 +1,23 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "coconut")] -use coconut_interface::CoconutError; -use crypto::asymmetric::encryption::KeyRecoveryError; +use nym_coconut_interface::CoconutError; +use nym_crypto::asymmetric::encryption::KeyRecoveryError; use validator_client::ValidatorClientError; use thiserror::Error; #[derive(Debug, Error)] pub enum Error { + #[error("IO error")] + IOError(#[from] std::io::Error), + #[error("The detailed description is yet to be determined")] BandwidthCredentialError, #[error("Could not contact any validator")] NoValidatorsAvailable, - #[cfg(feature = "coconut")] #[error("Ran into a coconut error - {0}")] CoconutError(#[from] CoconutError), @@ -31,4 +32,13 @@ pub enum Error { #[error("Could not parse the key - {0}")] ParsePublicKey(#[from] KeyRecoveryError), + + #[error("Could not gather enough signature shares. Try again using the recovery command")] + NotEnoughShares, + + #[error("Could not aggregate signature shares - {0}. Try again using the recovery command")] + SignatureAggregationError(CoconutError), + + #[error("Could not deserialize bandwidth voucher - {0}")] + BandwidthVoucherDeserializationError(String), } diff --git a/common/credentials/src/lib.rs b/common/credentials/src/lib.rs index 5eb08ed817..4f923ae059 100644 --- a/common/credentials/src/lib.rs +++ b/common/credentials/src/lib.rs @@ -1,11 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "coconut")] pub mod coconut; pub mod error; -#[cfg(not(feature = "coconut"))] -pub mod token; -#[cfg(feature = "coconut")] pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key}; diff --git a/common/credentials/src/token/bandwidth.rs b/common/credentials/src/token/bandwidth.rs deleted file mode 100644 index c3e6124160..0000000000 --- a/common/credentials/src/token/bandwidth.rs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crypto::asymmetric::identity::{PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; - -use crate::error::Error; -use std::convert::TryInto; - -pub struct TokenCredential { - verification_key: PublicKey, - gateway_identity: PublicKey, - bandwidth: u64, - signature: Signature, -} - -impl TokenCredential { - pub fn new( - verification_key: PublicKey, - gateway_identity: PublicKey, - bandwidth: u64, - signature: Signature, - ) -> Self { - TokenCredential { - verification_key, - gateway_identity, - bandwidth, - signature, - } - } - - pub fn verification_key(&self) -> PublicKey { - self.verification_key - } - - pub fn gateway_identity(&self) -> PublicKey { - self.gateway_identity - } - - pub fn bandwidth(&self) -> u64 { - self.bandwidth - } - - pub fn signature_bytes(&self) -> [u8; 64] { - self.signature.to_bytes() - } - - pub fn verify_signature(&self) -> bool { - let message: Vec = self - .verification_key - .to_bytes() - .iter() - .chain(self.gateway_identity.to_bytes().iter()) - .copied() - .collect(); - self.verification_key - .verify(&message, &self.signature) - .is_ok() - } - - pub fn to_bytes(&self) -> Vec { - self.verification_key - .to_bytes() - .iter() - .chain(self.gateway_identity.to_bytes().iter()) - .chain(self.bandwidth.to_be_bytes().iter()) - .chain(self.signature.to_bytes().iter()) - .copied() - .collect() - } - - pub fn from_bytes(b: &[u8]) -> Result { - if b.len() != 2 * PUBLIC_KEY_LENGTH + 8 + SIGNATURE_LENGTH { - return Err(Error::BandwidthCredentialError); - } - let verification_key = PublicKey::from_bytes(&b[..PUBLIC_KEY_LENGTH]) - .map_err(|_| Error::BandwidthCredentialError)?; - let gateway_identity = PublicKey::from_bytes(&b[PUBLIC_KEY_LENGTH..2 * PUBLIC_KEY_LENGTH]) - .map_err(|_| Error::BandwidthCredentialError)?; - let bandwidth = u64::from_be_bytes( - b[2 * PUBLIC_KEY_LENGTH..2 * PUBLIC_KEY_LENGTH + 8] - .try_into() - // unwrapping is safe because we know we have 8 bytes - .unwrap(), - ); - let signature = Signature::from_bytes(&b[2 * PUBLIC_KEY_LENGTH + 8..]) - .map_err(|_| Error::BandwidthCredentialError)?; - Ok(TokenCredential { - verification_key, - gateway_identity, - bandwidth, - signature, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn token_serde() { - // pre-generated, valid values - let verification_key = PublicKey::from_bytes(&[ - 103, 105, 71, 177, 149, 245, 26, 32, 73, 121, 76, 50, 94, 88, 119, 231, 91, 229, 167, - 56, 39, 62, 185, 39, 83, 246, 153, 27, 17, 155, 109, 73, - ]) - .unwrap(); - let gateway_identity = PublicKey::from_bytes(&[ - 37, 113, 137, 189, 157, 82, 35, 2, 187, 136, 61, 119, 98, 5, 245, 82, 46, 124, 67, 45, - 165, 255, 53, 222, 185, 252, 6, 148, 128, 15, 206, 19, - ]) - .unwrap(); - let signature = Signature::from_bytes(&[ - 117, 251, 162, 217, 57, 2, 50, 210, 206, 81, 236, 90, 74, 201, 69, 237, 240, 247, 214, - 158, 220, 89, 235, 222, 85, 134, 73, 73, 8, 60, 25, 39, 183, 28, 83, 193, 31, 174, 25, - 24, 38, 215, 205, 228, 159, 135, 35, 4, 171, 59, 100, 157, 12, 249, 77, 52, 143, 4, 32, - 28, 147, 70, 182, 14, - ]) - .unwrap(); - let credential = TokenCredential::new(verification_key, gateway_identity, 1024, signature); - let serialized_credential = credential.to_bytes(); - let deserialized_credential = TokenCredential::from_bytes(&serialized_credential).unwrap(); - assert_eq!( - credential.verification_key, - deserialized_credential.verification_key - ); - assert_eq!( - credential.gateway_identity, - deserialized_credential.gateway_identity - ); - assert_eq!(credential.bandwidth, deserialized_credential.bandwidth); - assert_eq!( - credential.signature.to_bytes(), - deserialized_credential.signature.to_bytes() - ); - } -} diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index a8e749f2e0..21ff8cb7a3 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -1,10 +1,11 @@ [package] -name = "crypto" -version = "0.1.0" -authors = ["Jedrzej Stuczynski // SPDX-License-Identifier: Apache-2.0 -use pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +use std::fmt::{self, Display, Formatter}; +use std::str::FromStr; +use thiserror::Error; + #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt::{self, Display, Formatter}; /// Size of a X25519 private key pub const PRIVATE_KEY_SIZE: usize = 32; @@ -17,31 +20,26 @@ pub const PUBLIC_KEY_SIZE: usize = 32; /// Size of a X25519 shared secret pub const SHARED_SECRET_SIZE: usize = 32; -#[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[derive(Clone, Eq, PartialEq, Debug, Error)] pub enum KeyRecoveryError { - InvalidPublicKeyBytes, - InvalidPrivateKeyBytes, - MalformedString(bs58::decode::Error), -} + #[error("received public key of invalid size. Got: {received}, expected: {expected}")] + InvalidSizePublicKey { received: usize, expected: usize }, -impl From for KeyRecoveryError { - fn from(err: bs58::decode::Error) -> Self { - KeyRecoveryError::MalformedString(err) - } -} + #[error("received private key of invalid size. Got: {received}, expected: {expected}")] + InvalidSizePrivateKey { received: usize, expected: usize }, -// required for std::error::Error -impl Display for KeyRecoveryError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - KeyRecoveryError::InvalidPrivateKeyBytes => write!(f, "Invalid private key bytes"), - KeyRecoveryError::InvalidPublicKeyBytes => write!(f, "Invalid public key bytes"), - KeyRecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err), - } - } -} + #[error("the base58 representation of the public key was malformed - {source}")] + MalformedPublicKeyString { + #[source] + source: bs58::decode::Error, + }, -impl std::error::Error for KeyRecoveryError {} + #[error("the base58 representation of the private key was malformed - {source}")] + MalformedPrivateKeyString { + #[source] + source: bs58::decode::Error, + }, +} pub struct KeyPair { pub(crate) private_key: PrivateKey, @@ -112,7 +110,10 @@ impl PublicKey { pub fn from_bytes(b: &[u8]) -> Result { if b.len() != PUBLIC_KEY_SIZE { - return Err(KeyRecoveryError::InvalidPublicKeyBytes); + return Err(KeyRecoveryError::InvalidSizePublicKey { + received: b.len(), + expected: PUBLIC_KEY_SIZE, + }); } let mut bytes = [0; PUBLIC_KEY_SIZE]; bytes.copy_from_slice(&b[..PUBLIC_KEY_SIZE]); @@ -124,11 +125,21 @@ impl PublicKey { } pub fn from_base58_string>(val: I) -> Result { - let bytes = bs58::decode(val).into_vec()?; + let bytes = bs58::decode(val) + .into_vec() + .map_err(|source| KeyRecoveryError::MalformedPublicKeyString { source })?; Self::from_bytes(&bytes) } } +impl FromStr for PublicKey { + type Err = KeyRecoveryError; + + fn from_str(s: &str) -> Result { + PublicKey::from_base58_string(s) + } +} + #[cfg(feature = "serde")] impl Serialize for PublicKey { fn serialize(&self, serializer: S) -> Result @@ -188,7 +199,10 @@ impl PrivateKey { pub fn from_bytes(b: &[u8]) -> Result { if b.len() != PRIVATE_KEY_SIZE { - return Err(KeyRecoveryError::InvalidPrivateKeyBytes); + return Err(KeyRecoveryError::InvalidSizePrivateKey { + received: b.len(), + expected: PRIVATE_KEY_SIZE, + }); } let mut bytes = [0; 32]; bytes.copy_from_slice(&b[..PRIVATE_KEY_SIZE]); @@ -200,7 +214,9 @@ impl PrivateKey { } pub fn from_base58_string>(val: I) -> Result { - let bytes = bs58::decode(val).into_vec()?; + let bytes = bs58::decode(val) + .into_vec() + .map_err(|source| KeyRecoveryError::MalformedPrivateKeyString { source })?; Self::from_bytes(&bytes) } @@ -249,38 +265,38 @@ impl PemStorableKey for PrivateKey { } // compatibility with sphinx keys: -impl From for nymsphinx_types::PublicKey { +impl From for nym_sphinx_types::PublicKey { fn from(key: PublicKey) -> Self { - nymsphinx_types::PublicKey::from(key.to_bytes()) + nym_sphinx_types::PublicKey::from(key.to_bytes()) } } -impl<'a> From<&'a PublicKey> for nymsphinx_types::PublicKey { +impl<'a> From<&'a PublicKey> for nym_sphinx_types::PublicKey { fn from(key: &'a PublicKey) -> Self { - nymsphinx_types::PublicKey::from((*key).to_bytes()) + nym_sphinx_types::PublicKey::from((*key).to_bytes()) } } -impl From for PublicKey { - fn from(pub_key: nymsphinx_types::PublicKey) -> Self { +impl From for PublicKey { + fn from(pub_key: nym_sphinx_types::PublicKey) -> Self { Self(x25519_dalek::PublicKey::from(*pub_key.as_bytes())) } } -impl From for nymsphinx_types::PrivateKey { +impl From for nym_sphinx_types::PrivateKey { fn from(key: PrivateKey) -> Self { - nymsphinx_types::PrivateKey::from(key.to_bytes()) + nym_sphinx_types::PrivateKey::from(key.to_bytes()) } } -impl<'a> From<&'a PrivateKey> for nymsphinx_types::PrivateKey { +impl<'a> From<&'a PrivateKey> for nym_sphinx_types::PrivateKey { fn from(key: &'a PrivateKey) -> Self { - nymsphinx_types::PrivateKey::from(key.to_bytes()) + nym_sphinx_types::PrivateKey::from(key.to_bytes()) } } -impl From for PrivateKey { - fn from(private_key: nymsphinx_types::PrivateKey) -> Self { +impl From for PrivateKey { + fn from(private_key: nym_sphinx_types::PrivateKey) -> Self { let private_key_bytes = private_key.to_bytes(); assert_eq!(private_key_bytes.len(), PRIVATE_KEY_SIZE); Self::from_bytes(&private_key_bytes).unwrap() @@ -305,10 +321,10 @@ mod sphinx_key_conversion { let private_bytes = private.to_bytes(); let public_bytes = public.to_bytes(); - let sphinx_private: nymsphinx_types::PrivateKey = private.into(); + let sphinx_private: nym_sphinx_types::PrivateKey = private.into(); let recovered_private = PrivateKey::from(sphinx_private); - let sphinx_public: nymsphinx_types::PublicKey = public.into(); + let sphinx_public: nym_sphinx_types::PublicKey = public.into(); let recovered_public = PublicKey::from(sphinx_public); assert_eq!(private_bytes, recovered_private.to_bytes()); assert_eq!(public_bytes, recovered_public.to_bytes()); @@ -318,16 +334,16 @@ mod sphinx_key_conversion { #[test] fn works_for_backward_conversion() { for _ in 0..NUM_ITERATIONS { - let (sphinx_private, sphinx_public) = nymsphinx_types::crypto::keygen(); + let (sphinx_private, sphinx_public) = nym_sphinx_types::crypto::keygen(); let private_bytes = sphinx_private.to_bytes(); let public_bytes = sphinx_public.as_bytes(); let private: PrivateKey = sphinx_private.into(); - let recovered_sphinx_private: nymsphinx_types::PrivateKey = private.into(); + let recovered_sphinx_private: nym_sphinx_types::PrivateKey = private.into(); let public: PublicKey = sphinx_public.into(); - let recovered_sphinx_public: nymsphinx_types::PublicKey = public.into(); + let recovered_sphinx_public: nym_sphinx_types::PublicKey = public.into(); assert_eq!(private_bytes, recovered_sphinx_private.to_bytes()); assert_eq!(public_bytes, recovered_sphinx_public.as_bytes()); } diff --git a/common/crypto/src/asymmetric/identity/mod.rs b/common/crypto/src/asymmetric/identity/mod.rs index 8ba1de8ec6..7a3fed7948 100644 --- a/common/crypto/src/asymmetric/identity/mod.rs +++ b/common/crypto/src/asymmetric/identity/mod.rs @@ -4,12 +4,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 nymsphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; -use pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; +use std::fmt::{self, Display, Formatter}; +use std::str::FromStr; +use thiserror::Error; + #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; -use std::fmt::{self, Display, Formatter}; - #[cfg(feature = "serde")] use serde::de::Error as SerdeError; #[cfg(feature = "serde")] @@ -17,35 +19,30 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "serde")] use serde_bytes::{ByteBuf as SerdeByteBuf, Bytes as SerdeBytes}; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum Ed25519RecoveryError { - MalformedBytes(SignatureError), - MalformedString(bs58::decode::Error), -} + #[error(transparent)] + MalformedBytes(#[from] SignatureError), -impl From for Ed25519RecoveryError { - fn from(err: SignatureError) -> Self { - Ed25519RecoveryError::MalformedBytes(err) - } -} + #[error("the base58 representation of the public key was malformed - {source}")] + MalformedPublicKeyString { + #[source] + source: bs58::decode::Error, + }, -impl From for Ed25519RecoveryError { - fn from(err: bs58::decode::Error) -> Self { - Ed25519RecoveryError::MalformedString(err) - } -} + #[error("the base58 representation of the private key was malformed - {source}")] + MalformedPrivateKeyString { + #[source] + source: bs58::decode::Error, + }, -impl fmt::Display for Ed25519RecoveryError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Ed25519RecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err), - Ed25519RecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err), - } - } + #[error("the base58 representation of the signature was malformed - {source}")] + MalformedSignatureString { + #[source] + source: bs58::decode::Error, + }, } -impl std::error::Error for Ed25519RecoveryError {} - /// Keypair for usage in ed25519 EdDSA. #[derive(Debug)] pub struct KeyPair { @@ -134,7 +131,9 @@ impl PublicKey { } pub fn from_base58_string>(val: I) -> Result { - let bytes = bs58::decode(val).into_vec()?; + let bytes = bs58::decode(val) + .into_vec() + .map_err(|source| Ed25519RecoveryError::MalformedPublicKeyString { source })?; Self::from_bytes(&bytes) } @@ -143,6 +142,14 @@ impl PublicKey { } } +impl FromStr for PublicKey { + type Err = Ed25519RecoveryError; + + fn from_str(s: &str) -> Result { + PublicKey::from_base58_string(s) + } +} + #[cfg(feature = "serde")] impl Serialize for PublicKey { fn serialize(&self, serializer: S) -> Result @@ -211,7 +218,9 @@ impl PrivateKey { } pub fn from_base58_string>(val: I) -> Result { - let bytes = bs58::decode(val).into_vec()?; + let bytes = bs58::decode(val) + .into_vec() + .map_err(|source| Ed25519RecoveryError::MalformedPrivateKeyString { source })?; Self::from_bytes(&bytes) } @@ -277,7 +286,9 @@ impl Signature { } pub fn from_base58_string>(val: I) -> Result { - let bytes = bs58::decode(val).into_vec()?; + let bytes = bs58::decode(val) + .into_vec() + .map_err(|source| Ed25519RecoveryError::MalformedSignatureString { source })?; Self::from_bytes(&bytes) } diff --git a/common/crypto/src/bech32_address_validation.rs b/common/crypto/src/bech32_address_validation.rs index f432daab4c..de6e298c66 100644 --- a/common/crypto/src/bech32_address_validation.rs +++ b/common/crypto/src/bech32_address_validation.rs @@ -21,8 +21,7 @@ pub fn validate_bech32_prefix(bech32_prefix: &str, address: &str) -> Result<(), Ok(()) } else { Err(Bech32Error::WrongPrefix(format!( - "your bech32 address prefix should be {}, not {}", - bech32_prefix, prefix + "your bech32 address prefix should be {bech32_prefix}, not {prefix}" ))) } } @@ -70,8 +69,7 @@ mod tests { fn wrong_prefix_fails() { assert_eq!( Err(Bech32Error::WrongPrefix(format!( - "your bech32 address prefix should be {}, not punk", - TEST_BECH32_PREFIX + "your bech32 address prefix should be {TEST_BECH32_PREFIX}, not punk" ))), validate_bech32_prefix( TEST_BECH32_PREFIX, diff --git a/common/crypto/dkg/Cargo.toml b/common/dkg/Cargo.toml similarity index 81% rename from common/crypto/dkg/Cargo.toml rename to common/dkg/Cargo.toml index ea20508321..dc1f4c29e9 100644 --- a/common/crypto/dkg/Cargo.toml +++ b/common/dkg/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "dkg" +name = "nym-dkg" version = "0.1.0" edition = "2021" resolver = "2" @@ -12,9 +12,10 @@ 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"] } - +nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common", optional = true } bs58 = "0.4" + lazy_static = "1.4.0" rand = { version = "0.8.5", default-features = false} rand_chacha = "0.3" @@ -25,7 +26,7 @@ serde_derive = "1.0" thiserror = "1.0" zeroize = { version = "1.4", features = ["zeroize_derive"] } -pemstore = { path = "../../pemstore" } +nym-pemstore = { path = "../pemstore" } [dependencies.group] version = "0.11" @@ -36,8 +37,12 @@ version = "0.11" default-features = false [dev-dependencies] -criterion = "0.3" +criterion = "0.4" [[bench]] name = "benchmarks" harness = false + +[features] +default = [] +cw-types = ["nym-contracts-common"] diff --git a/common/crypto/dkg/benches/benchmarks.rs b/common/dkg/benches/benchmarks.rs similarity index 98% rename from common/crypto/dkg/benches/benchmarks.rs rename to common/dkg/benches/benchmarks.rs index 7b85d70356..0aed2e4192 100644 --- a/common/crypto/dkg/benches/benchmarks.rs +++ b/common/dkg/benches/benchmarks.rs @@ -3,17 +3,17 @@ use bls12_381::{G1Projective, G2Affine, G2Prepared, Scalar}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use dkg::bte::encryption::BabyStepGiantStepLookup; -use dkg::bte::proof_chunking::ProofOfChunking; -use dkg::bte::proof_discrete_log::ProofOfDiscreteLog; -use dkg::bte::proof_sharing::ProofOfSecretSharing; -use dkg::bte::{ +use ff::Field; +use nym_dkg::bte::encryption::BabyStepGiantStepLookup; +use nym_dkg::bte::proof_chunking::ProofOfChunking; +use nym_dkg::bte::proof_discrete_log::ProofOfDiscreteLog; +use nym_dkg::bte::proof_sharing::ProofOfSecretSharing; +use nym_dkg::bte::{ decrypt_share, encrypt_shares, keygen, proof_chunking, proof_sharing, setup, DecryptionKey, PublicKey, }; -use dkg::interpolation::polynomial::Polynomial; -use dkg::{Dealing, NodeIndex, Share}; -use ff::Field; +use nym_dkg::interpolation::polynomial::Polynomial; +use nym_dkg::{Dealing, NodeIndex, Share}; use rand_core::{RngCore, SeedableRng}; use std::collections::BTreeMap; diff --git a/common/crypto/dkg/src/bte/encryption.rs b/common/dkg/src/bte/encryption.rs similarity index 100% rename from common/crypto/dkg/src/bte/encryption.rs rename to common/dkg/src/bte/encryption.rs diff --git a/common/crypto/dkg/src/bte/keys.rs b/common/dkg/src/bte/keys.rs similarity index 97% rename from common/crypto/dkg/src/bte/keys.rs rename to common/dkg/src/bte/keys.rs index 098e9638c7..8d682df757 100644 --- a/common/crypto/dkg/src/bte/keys.rs +++ b/common/dkg/src/bte/keys.rs @@ -8,7 +8,7 @@ use crate::utils::{deserialize_g1, deserialize_g2, deserialize_scalar}; use bls12_381::{G1Projective, G2Projective, Scalar}; use ff::Field; use group::GroupEncoding; -use pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use rand_core::RngCore; use zeroize::Zeroize; @@ -224,10 +224,7 @@ impl DecryptionKey { let mut dh = Vec::with_capacity(dh_len); for j in 0..dh_len { let dh_i = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| { - DkgError::new_deserialization_failure( - format!("Node.dh_{}", j), - "invalid curve point", - ) + DkgError::new_deserialization_failure(format!("Node.dh_{j}"), "invalid curve point") })?; dh.push(dh_i); diff --git a/common/crypto/dkg/src/bte/mod.rs b/common/dkg/src/bte/mod.rs similarity index 97% rename from common/crypto/dkg/src/bte/mod.rs rename to common/dkg/src/bte/mod.rs index 2eca744cf1..0bb18dc082 100644 --- a/common/crypto/dkg/src/bte/mod.rs +++ b/common/dkg/src/bte/mod.rs @@ -60,7 +60,7 @@ pub fn setup() -> Params { let f0 = hash_g2(b"f0", SETUP_DOMAIN); let fh = (0..HASH_SECURITY_PARAM) - .map(|i| hash_g2(format!("fh{}", i), SETUP_DOMAIN)) + .map(|i| hash_g2(format!("fh{i}"), SETUP_DOMAIN)) .collect(); let h = hash_g2(b"h", SETUP_DOMAIN); diff --git a/common/crypto/dkg/src/bte/proof_chunking.rs b/common/dkg/src/bte/proof_chunking.rs similarity index 100% rename from common/crypto/dkg/src/bte/proof_chunking.rs rename to common/dkg/src/bte/proof_chunking.rs diff --git a/common/crypto/dkg/src/bte/proof_discrete_log.rs b/common/dkg/src/bte/proof_discrete_log.rs similarity index 100% rename from common/crypto/dkg/src/bte/proof_discrete_log.rs rename to common/dkg/src/bte/proof_discrete_log.rs diff --git a/common/crypto/dkg/src/bte/proof_sharing.rs b/common/dkg/src/bte/proof_sharing.rs similarity index 100% rename from common/crypto/dkg/src/bte/proof_sharing.rs rename to common/dkg/src/bte/proof_sharing.rs diff --git a/common/crypto/dkg/src/dealing.rs b/common/dkg/src/dealing.rs similarity index 97% rename from common/crypto/dkg/src/dealing.rs rename to common/dkg/src/dealing.rs index b3633ec82d..ff31c5736e 100644 --- a/common/crypto/dkg/src/dealing.rs +++ b/common/dkg/src/dealing.rs @@ -307,6 +307,24 @@ impl Dealing { } } +#[cfg(feature = "cw-types")] +impl<'a> From<&'a Dealing> for nym_contracts_common::dealings::ContractSafeBytes { + fn from(dealing: &'a Dealing) -> Self { + nym_contracts_common::dealings::ContractSafeBytes(dealing.to_bytes()) + } +} + +#[cfg(feature = "cw-types")] +impl<'a> TryFrom<&'a nym_contracts_common::dealings::ContractSafeBytes> for Dealing { + type Error = DkgError; + + fn try_from( + value: &'a nym_contracts_common::dealings::ContractSafeBytes, + ) -> Result { + Dealing::try_from_bytes(&value.0) + } +} + // this assumes all dealings have been verified pub fn try_recover_verification_keys( dealings: &[Dealing], @@ -326,11 +344,6 @@ pub fn try_recover_verification_keys( return Err(DkgError::MismatchedDealings); } - // currently we expect every dealer to also be a receiver. This restriction might be relaxed in the future - if dealings.len() != receivers.len() { - return Err(DkgError::MismatchedDealings); - } - let indices = receivers.keys().collect::>(); // Compute A0, ..., A_{t-1} diff --git a/common/crypto/dkg/src/error.rs b/common/dkg/src/error.rs similarity index 100% rename from common/crypto/dkg/src/error.rs rename to common/dkg/src/error.rs diff --git a/common/crypto/dkg/src/interpolation/mod.rs b/common/dkg/src/interpolation/mod.rs similarity index 100% rename from common/crypto/dkg/src/interpolation/mod.rs rename to common/dkg/src/interpolation/mod.rs diff --git a/common/crypto/dkg/src/interpolation/polynomial.rs b/common/dkg/src/interpolation/polynomial.rs similarity index 100% rename from common/crypto/dkg/src/interpolation/polynomial.rs rename to common/dkg/src/interpolation/polynomial.rs diff --git a/common/crypto/dkg/src/lib.rs b/common/dkg/src/lib.rs similarity index 100% rename from common/crypto/dkg/src/lib.rs rename to common/dkg/src/lib.rs diff --git a/common/crypto/dkg/src/share.rs b/common/dkg/src/share.rs similarity index 100% rename from common/crypto/dkg/src/share.rs rename to common/dkg/src/share.rs diff --git a/common/crypto/dkg/src/todos.md b/common/dkg/src/todos.md similarity index 100% rename from common/crypto/dkg/src/todos.md rename to common/dkg/src/todos.md diff --git a/common/crypto/dkg/src/utils.rs b/common/dkg/src/utils.rs similarity index 100% rename from common/crypto/dkg/src/utils.rs rename to common/dkg/src/utils.rs diff --git a/common/crypto/dkg/tests/integration.rs b/common/dkg/tests/integration.rs similarity index 66% rename from common/crypto/dkg/tests/integration.rs rename to common/dkg/tests/integration.rs index 2d25c31d40..2e7ffcda07 100644 --- a/common/crypto/dkg/tests/integration.rs +++ b/common/dkg/tests/integration.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use bls12_381::{G2Projective, Scalar}; -use dkg::bte::{decrypt_share, keygen, setup}; -use dkg::dealing::RecoveredVerificationKeys; -use dkg::interpolation::perform_lagrangian_interpolation_at_origin; -use dkg::{combine_shares, try_recover_verification_keys, Dealing}; +use nym_dkg::bte::{decrypt_share, keygen, setup}; +use nym_dkg::dealing::RecoveredVerificationKeys; +use nym_dkg::interpolation::perform_lagrangian_interpolation_at_origin; +use nym_dkg::{combine_shares, try_recover_verification_keys, Dealing}; use rand_core::SeedableRng; use std::collections::BTreeMap; @@ -254,3 +254,125 @@ fn full_threshold_secret_resharing() { assert_ne!(derived_secrets, reshared_secrets); assert_ne!(recovered_partials, reshared_partials); } + +#[test] +#[ignore] // expensive test +fn full_threshold_secret_resharing_left_party() { + let dummy_seed = [42u8; 32]; + let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed); + let params = setup(); + + // the simplest possible case + let threshold = 2; + + // the indices are going to get assigned externally, so for test sake, use non-consecutive ones + let mut node_indices = vec![15u64, 248, 33521]; + + let mut receivers = BTreeMap::new(); + let mut full_keys = Vec::new(); + for index in &node_indices { + let (dk, pk) = keygen(¶ms, &mut rng); + receivers.insert(*index, *pk.public_key()); + full_keys.push((dk, pk)) + } + + let first_dealings = node_indices + .iter() + .map(|&dealer_index| { + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + }) + .collect::>(); + + // recover verification keys + let RecoveredVerificationKeys { + recovered_master: public_original_master, + recovered_partials, + } = try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap(); + + let mut derived_secrets = Vec::new(); + for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + let shares = first_dealings + .iter() + .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) + .collect(); + + let recovered_secret = + combine_shares(shares, &receivers.keys().copied().collect::>()).unwrap(); + + derived_secrets.push(recovered_secret) + } + + let original_master = perform_lagrangian_interpolation_at_origin(&[ + (Scalar::from(node_indices[0]), derived_secrets[0]), + (Scalar::from(node_indices[1]), derived_secrets[1]), + ]) + .unwrap(); + + // one party leaves the process + let left_party_index = node_indices.pop().unwrap(); + receivers.remove(&left_party_index); + full_keys.pop(); + // and another one joins, but we're still over the threshold value of initial parties + let join_party_index = 100000; + let (dk, pk) = keygen(¶ms, &mut rng); + receivers.insert(join_party_index, *pk.public_key()); + full_keys.push((dk, pk)); + + // only initial parties attempt to create resharing dealings! + let resharing_dealings = node_indices + .iter() + .zip(derived_secrets.iter().take(2)) + .map(|(&dealer_index, prior_secret)| { + Dealing::create( + &mut rng, + ¶ms, + dealer_index, + threshold, + &receivers, + Some(*prior_secret), + ) + .0 + }) + .collect::>(); + + for (reshared_dealing, prior_vk) in resharing_dealings + .iter() + .zip(recovered_partials.iter().take(2)) + { + reshared_dealing + .verify(¶ms, threshold, &receivers, Some(*prior_vk)) + .unwrap(); + } + + // recover verification keys + let RecoveredVerificationKeys { + recovered_master: public_reshared_master, + recovered_partials: reshared_partials, + } = try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap(); + + let mut reshared_secrets = Vec::new(); + for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + let shares = resharing_dealings + .iter() + .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) + .collect(); + + let recovered_secret = combine_shares(shares, &node_indices).unwrap(); + + reshared_secrets.push(recovered_secret) + } + + let reshared_master = perform_lagrangian_interpolation_at_origin(&[ + (Scalar::from(node_indices[0]), reshared_secrets[0]), + (Scalar::from(join_party_index), reshared_secrets[2]), + ]) + .unwrap(); + + // the master secret and public values didn't change + assert_eq!(original_master, reshared_master); + assert_eq!(public_original_master, public_reshared_master); + + // but partials did + assert_ne!(derived_secrets, reshared_secrets); + assert_ne!(recovered_partials, reshared_partials); +} diff --git a/common/execute/Cargo.toml b/common/execute/Cargo.toml index 551f1728fb..634737397f 100644 --- a/common/execute/Cargo.toml +++ b/common/execute/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "execute" +name = "nym-execute" version = "0.1.0" edition = "2021" diff --git a/common/execute/src/lib.rs b/common/execute/src/lib.rs index 795a72822d..c05a2e8748 100644 --- a/common/execute/src/lib.rs +++ b/common/execute/src/lib.rs @@ -53,13 +53,13 @@ pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream { ); let execute_output = quote! { - -> Result + -> Result }; let o_ts = proc_macro::TokenStream::from(execute_output); execute_f.sig.output = parse_macro_input!(o_ts as ReturnType); let simulate_output = quote! { - -> Result + -> Result }; let o_ts = proc_macro::TokenStream::from(simulate_output); simulate_f.sig.output = parse_macro_input!(o_ts as ReturnType); diff --git a/common/inclusion-probability/Cargo.toml b/common/inclusion-probability/Cargo.toml index 73eedb75a0..b98a1ec5e8 100644 --- a/common/inclusion-probability/Cargo.toml +++ b/common/inclusion-probability/Cargo.toml @@ -1,11 +1,13 @@ [package] -name = "inclusion-probability" +name = "nym-inclusion-probability" version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "Nym active set probability simulator" +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true [dependencies] -log = "0.4.17" -rand = "0.8.5" -thiserror = "1.0.32" +log = { workspace = true } +rand = { workspace = true } +thiserror = { workspace = true } diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml index d7b41990e3..c0ce1b2a42 100644 --- a/common/mixnode-common/Cargo.toml +++ b/common/mixnode-common/Cargo.toml @@ -10,21 +10,23 @@ edition = "2021" bytes = "1.0" futures = "0.3" humantime-serde = "1.0" -log = "0.4" +log = { workspace = true } rand = "0.8" serde = { version = "1.0", features = ["derive"] } -tokio = { version = "1.21.2", features = ["time", "macros", "rt", "net", "io-util"] } -tokio-util = { version = "0.7.3", features = ["codec"] } +tokio = { version = "1.24.1", features = ["time", "macros", "rt", "net", "io-util"] } +tokio-util = { version = "0.7.4", features = ["codec"] } url = "2.2" tracing = "0.1.37" +thiserror = "1.0.37" -crypto = { path = "../crypto" } -nymsphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" } -nymsphinx-addressing = { path = "../nymsphinx/addressing" } -nymsphinx-forwarding = { path = "../nymsphinx/forwarding" } -nymsphinx-framing = { path = "../nymsphinx/framing" } -nymsphinx-params = { path = "../nymsphinx/params" } -nymsphinx-types = { path = "../nymsphinx/types" } -task = { path = "../task" } -validator-client = { path = "../client-libs/validator-client" } -version-checker = { path = "../version-checker" } +nym-crypto = { path = "../crypto" } +nym-network-defaults = { path = "../network-defaults" } +nym-sphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" } +nym-sphinx-addressing = { path = "../nymsphinx/addressing" } +nym-sphinx-forwarding = { path = "../nymsphinx/forwarding" } +nym-sphinx-framing = { path = "../nymsphinx/framing" } +nym-sphinx-params = { path = "../nymsphinx/params" } +nym-sphinx-types = { path = "../nymsphinx/types" } +nym-task = { path = "../task" } +validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"]} +nym-bin-common = { path = "../bin-common" } diff --git a/common/mixnode-common/src/packet_processor/error.rs b/common/mixnode-common/src/packet_processor/error.rs index 38be92079d..99d3a2d2f2 100644 --- a/common/mixnode-common/src/packet_processor/error.rs +++ b/common/mixnode-common/src/packet_processor/error.rs @@ -1,66 +1,25 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nymsphinx_acknowledgements::surb_ack::SurbAckRecoveryError; -use nymsphinx_addressing::nodes::NymNodeRoutingAddressError; -use nymsphinx_types::Error as SphinxError; -use std::fmt::{self, Display, Formatter}; +use nym_sphinx_acknowledgements::surb_ack::SurbAckRecoveryError; +use nym_sphinx_addressing::nodes::NymNodeRoutingAddressError; +use nym_sphinx_types::Error as SphinxError; +use thiserror::Error; -#[derive(Debug)] +#[derive(Error, Debug)] pub enum MixProcessingError { - SphinxProcessingError(SphinxError), - InvalidHopAddress(NymNodeRoutingAddressError), - NoSurbAckInFinalHop, - MalformedSurbAck(SurbAckRecoveryError), + #[error("failed to process received packet: {0}")] + SphinxProcessingError(#[from] SphinxError), + #[error("the forward hop address was malformed: {0}")] + InvalidForwardHopAddress(#[from] NymNodeRoutingAddressError), + + #[error("the final hop did not contain a SURB-Ack")] + NoSurbAckInFinalHop, + + #[error("failed to recover the expected SURB-Ack packet: {0}")] + MalformedSurbAck(#[from] SurbAckRecoveryError), + + #[error("the received packet was set to use the very old and very much deprecated 'VPN' mode")] ReceivedOldTypeVpnPacket, } - -impl From for MixProcessingError { - // for the time being just have a single error instance for all possible results of SphinxError - fn from(err: SphinxError) -> Self { - use MixProcessingError::*; - - SphinxProcessingError(err) - } -} - -impl From for MixProcessingError { - fn from(err: NymNodeRoutingAddressError) -> Self { - use MixProcessingError::*; - - InvalidHopAddress(err) - } -} - -impl From for MixProcessingError { - fn from(err: SurbAckRecoveryError) -> Self { - use MixProcessingError::*; - - MalformedSurbAck(err) - } -} - -impl Display for MixProcessingError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - MixProcessingError::SphinxProcessingError(sphinx_err) => { - write!(f, "Sphinx Processing Error - {}", sphinx_err) - } - MixProcessingError::InvalidHopAddress(address_err) => { - write!(f, "Invalid Hop Address - {:?}", address_err) - } - MixProcessingError::NoSurbAckInFinalHop => { - write!(f, "No SURBAck present in the final hop data") - } - MixProcessingError::MalformedSurbAck(surb_ack_err) => { - write!(f, "Malformed SURBAck - {:?}", surb_ack_err) - } - MixProcessingError::ReceivedOldTypeVpnPacket => { - write!(f, "Received an old-type unsafe 'VPN' mode packet") - } - } - } -} - -impl std::error::Error for MixProcessingError {} diff --git a/common/mixnode-common/src/packet_processor/processor.rs b/common/mixnode-common/src/packet_processor/processor.rs index 7a925f2def..2d01658b4f 100644 --- a/common/mixnode-common/src/packet_processor/processor.rs +++ b/common/mixnode-common/src/packet_processor/processor.rs @@ -3,12 +3,12 @@ use crate::packet_processor::error::MixProcessingError; use tracing::*; -use nymsphinx_acknowledgements::surb_ack::SurbAck; -use nymsphinx_addressing::nodes::NymNodeRoutingAddress; -use nymsphinx_forwarding::packet::MixPacket; -use nymsphinx_framing::packet::FramedSphinxPacket; -use nymsphinx_params::{PacketMode, PacketSize}; -use nymsphinx_types::{ +use nym_sphinx_acknowledgements::surb_ack::SurbAck; +use nym_sphinx_addressing::nodes::NymNodeRoutingAddress; +use nym_sphinx_forwarding::packet::MixPacket; +use nym_sphinx_framing::packet::FramedSphinxPacket; +use nym_sphinx_params::{PacketMode, PacketSize}; +use nym_sphinx_types::{ Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, Payload, PrivateKey, ProcessedPacket, SphinxPacket, }; @@ -51,7 +51,7 @@ impl SphinxPacketProcessor { packet: SphinxPacket, ) -> Result { packet.process(&self.sphinx_key).map_err(|err| { - debug!("Failed to unwrap Sphinx packet: {:?}", err); + debug!("Failed to unwrap Sphinx packet: {err}"); MixProcessingError::SphinxProcessingError(err) }) } @@ -195,7 +195,7 @@ impl SphinxPacketProcessor { #[cfg(test)] mod tests { use super::*; - use nymsphinx_types::crypto::keygen; + use nym_sphinx_types::crypto::keygen; fn fixture() -> SphinxPacketProcessor { let local_keys = keygen(); diff --git a/common/mixnode-common/src/verloc/error.rs b/common/mixnode-common/src/verloc/error.rs index 44a408c77a..4bfe0da48e 100644 --- a/common/mixnode-common/src/verloc/error.rs +++ b/common/mixnode-common/src/verloc/error.rs @@ -53,19 +53,19 @@ impl Display for RttError { write!(f, "The received reply packet had invalid signature") } RttError::UnreachableNode(id, err) => { - write!(f, "Could not establish connection to {} - {}", id, err) + write!(f, "Could not establish connection to {id} - {err}") } RttError::UnexpectedConnectionFailureWrite(id, err) => { - write!(f, "Failed to write echo packet to {} - {}", id, err) + write!(f, "Failed to write echo packet to {id} - {err}") } RttError::UnexpectedConnectionFailureRead(id, err) => { - write!(f, "Failed to read reply packet from {} - {}", id, err) + write!(f, "Failed to read reply packet from {id} - {err}") } RttError::ConnectionReadTimeout(id) => { - write!(f, "Timed out while trying to read reply packet from {}", id) + write!(f, "Timed out while trying to read reply packet from {id}") } RttError::ConnectionWriteTimeout(id) => { - write!(f, "Timed out while trying to write echo packet to {}", id) + write!(f, "Timed out while trying to write echo packet to {id}") } RttError::UnexpectedReplySequence => write!( f, diff --git a/common/mixnode-common/src/verloc/listener.rs b/common/mixnode-common/src/verloc/listener.rs index 8f49779130..a41e4edc8f 100644 --- a/common/mixnode-common/src/verloc/listener.rs +++ b/common/mixnode-common/src/verloc/listener.rs @@ -4,14 +4,14 @@ use crate::verloc::error::RttError; use crate::verloc::packet::{EchoPacket, ReplyPacket}; use bytes::{BufMut, BytesMut}; -use crypto::asymmetric::identity; use futures::StreamExt; use log::*; +use nym_crypto::asymmetric::identity; +use nym_task::TaskClient; use std::fmt::{Display, Formatter}; use std::net::SocketAddr; use std::sync::Arc; use std::{fmt, io, process}; -use task::ShutdownListener; use tokio::io::AsyncWriteExt; use tokio::net::{TcpListener, TcpStream}; use tokio_util::codec::{Decoder, Encoder, Framed}; @@ -19,14 +19,14 @@ use tokio_util::codec::{Decoder, Encoder, Framed}; pub(crate) struct PacketListener { address: SocketAddr, connection_handler: Arc, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl PacketListener { pub(crate) fn new( address: SocketAddr, identity: Arc, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { PacketListener { address, @@ -56,7 +56,8 @@ impl PacketListener { while !shutdown_listener.is_shutdown() { // cloning the arc as each accepted socket is handled in separate task let connection_handler = Arc::clone(&self.connection_handler); - let handler_shutdown_listener = self.shutdown.clone(); + let mut handler_shutdown_listener = self.shutdown.clone(); + handler_shutdown_listener.mark_as_success(); tokio::select! { socket = listener.accept() => { @@ -66,7 +67,7 @@ impl PacketListener { tokio::spawn(connection_handler.handle_connection(socket, remote_addr, handler_shutdown_listener)); } - Err(err) => warn!("Failed to accept incoming connection - {:?}", err), + Err(err) => warn!("Failed to accept incoming connection - {err}"), } }, _ = shutdown_listener.recv() => { @@ -91,7 +92,7 @@ impl ConnectionHandler { self: Arc, conn: TcpStream, remote: SocketAddr, - mut shutdown_listener: ShutdownListener, + mut shutdown_listener: TaskClient, ) { debug!("Starting connection handler for {:?}", remote); @@ -141,9 +142,9 @@ enum EchoPacketCodecError { impl Display for EchoPacketCodecError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - EchoPacketCodecError::IoError(err) => write!(f, "encountered io error - {}", err), + EchoPacketCodecError::IoError(err) => write!(f, "encountered io error - {err}"), EchoPacketCodecError::PacketRecoveryError(err) => { - write!(f, "failed to correctly decode an echo packet - {}", err) + write!(f, "failed to correctly decode an echo packet - {err}") } } } diff --git a/common/mixnode-common/src/verloc/measurement.rs b/common/mixnode-common/src/verloc/measurement.rs index 5c23d523a5..6f3d93bb65 100644 --- a/common/mixnode-common/src/verloc/measurement.rs +++ b/common/mixnode-common/src/verloc/measurement.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crypto::asymmetric::identity; +use nym_crypto::asymmetric::identity; use serde::{Serialize, Serializer}; use std::cmp::Ordering; use std::fmt::{self, Display, Formatter}; diff --git a/common/mixnode-common/src/verloc/mod.rs b/common/mixnode-common/src/verloc/mod.rs index 48e9a7b483..336d8a20e9 100644 --- a/common/mixnode-common/src/verloc/mod.rs +++ b/common/mixnode-common/src/verloc/mod.rs @@ -3,20 +3,22 @@ use crate::verloc::listener::PacketListener; use crate::verloc::sender::{PacketSender, TestedNode}; -use crypto::asymmetric::identity; use futures::stream::FuturesUnordered; use futures::StreamExt; use log::*; +use nym_bin_common::version_checker::{self, parse_version}; +use nym_crypto::asymmetric::identity; +use nym_network_defaults::mainnet::NYM_API; +use nym_task::TaskClient; use rand::seq::SliceRandom; use rand::thread_rng; -use std::net::{SocketAddr, ToSocketAddrs}; +use std::net::SocketAddr; +use std::net::ToSocketAddrs; use std::sync::Arc; use std::time::Duration; -use task::ShutdownListener; use tokio::task::JoinHandle; use tokio::time::sleep; use url::Url; -use version_checker::parse_version; pub use crate::verloc::measurement::{AtomicVerlocResult, Verloc, VerlocResult}; @@ -69,8 +71,8 @@ pub struct Config { /// due to being unable to get the list of nodes. retry_timeout: Duration, - /// URLs to the validator apis for obtaining network topology. - validator_api_urls: Vec, + /// URLs to the nym apis for obtaining network topology. + nym_api_urls: Vec, } impl Config { @@ -132,15 +134,15 @@ impl ConfigBuilder { self } - pub fn validator_api_urls(mut self, validator_api_urls: Vec) -> Self { - self.0.validator_api_urls = validator_api_urls; + pub fn nym_api_urls(mut self, nym_api_urls: Vec) -> Self { + self.0.nym_api_urls = nym_api_urls; self } pub fn build(self) -> Config { // panics here are fine as those are only ever constructed at the initial setup assert!( - !self.0.validator_api_urls.is_empty(), + !self.0.nym_api_urls.is_empty(), "at least one validator endpoint must be provided", ); self.0 @@ -151,7 +153,7 @@ impl Default for ConfigBuilder { fn default() -> Self { ConfigBuilder(Config { minimum_compatible_node_version: parse_version(MINIMUM_NODE_VERSION).unwrap(), - listening_address: format!("[::]:{}", DEFAULT_VERLOC_PORT).parse().unwrap(), + listening_address: format!("[::]:{DEFAULT_VERLOC_PORT}").parse().unwrap(), packets_per_node: DEFAULT_PACKETS_PER_NODE, packet_timeout: DEFAULT_PACKET_TIMEOUT, connection_timeout: DEFAULT_CONNECTION_TIMEOUT, @@ -159,7 +161,7 @@ impl Default for ConfigBuilder { tested_nodes_batch_size: DEFAULT_BATCH_SIZE, testing_interval: DEFAULT_TESTING_INTERVAL, retry_timeout: DEFAULT_RETRY_TIMEOUT, - validator_api_urls: vec![], + nym_api_urls: vec![NYM_API.parse().expect("Invalid default API URL")], }) } } @@ -168,7 +170,7 @@ pub struct VerlocMeasurer { config: Config, packet_sender: Arc, packet_listener: Arc, - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, currently_used_api: usize, @@ -176,7 +178,7 @@ pub struct VerlocMeasurer { // It only does bunch of REST queries. If we update it at some point to a more sophisticated (maybe signing) client, // then it definitely cannot be constructed here and probably will need to be passed from outside, // as mixnodes/gateways would already be using an instance of said client. - validator_client: validator_client::ApiClient, + validator_client: validator_client::NymApiClient, results: AtomicVerlocResult, } @@ -184,9 +186,9 @@ impl VerlocMeasurer { pub fn new( mut config: Config, identity: Arc, - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, ) -> Self { - config.validator_api_urls.shuffle(&mut thread_rng()); + config.nym_api_urls.shuffle(&mut thread_rng()); VerlocMeasurer { packet_sender: Arc::new(PacketSender::new( @@ -204,24 +206,21 @@ impl VerlocMeasurer { )), shutdown_listener, currently_used_api: 0, - validator_client: validator_client::ApiClient::new( - config.validator_api_urls[0].clone(), - ), + validator_client: validator_client::NymApiClient::new(config.nym_api_urls[0].clone()), config, results: AtomicVerlocResult::new(), } } - fn use_next_validator_api(&mut self) { - if self.config.validator_api_urls.len() == 1 { + fn use_next_nym_api(&mut self) { + if self.config.nym_api_urls.len() == 1 { warn!("There's only a single validator API available - it won't be possible to use a different one"); return; } - self.currently_used_api = - (self.currently_used_api + 1) % self.config.validator_api_urls.len(); + self.currently_used_api = (self.currently_used_api + 1) % self.config.nym_api_urls.len(); self.validator_client - .change_validator_api(self.config.validator_api_urls[self.currently_used_api].clone()) + .change_nym_api(self.config.nym_api_urls[self.currently_used_api].clone()) } pub fn get_verloc_results_pointer(&self) -> AtomicVerlocResult { @@ -308,7 +307,7 @@ impl VerlocMeasurer { "failed to obtain list of mixnodes from the validator - {}. Going to attempt to use another validator API in the next run", err ); - self.use_next_validator_api(); + self.use_next_nym_api(); sleep(self.config.retry_timeout).await; continue; } diff --git a/common/mixnode-common/src/verloc/packet.rs b/common/mixnode-common/src/verloc/packet.rs index ad91aa7fda..80d7f59ac9 100644 --- a/common/mixnode-common/src/verloc/packet.rs +++ b/common/mixnode-common/src/verloc/packet.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::verloc::error::RttError; -use crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; +use nym_crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; use std::convert::TryInto; pub(crate) struct EchoPacket { diff --git a/common/mixnode-common/src/verloc/sender.rs b/common/mixnode-common/src/verloc/sender.rs index bed34f4474..665acc3145 100644 --- a/common/mixnode-common/src/verloc/sender.rs +++ b/common/mixnode-common/src/verloc/sender.rs @@ -4,14 +4,14 @@ use crate::verloc::error::RttError; use crate::verloc::measurement::Measurement; use crate::verloc::packet::{EchoPacket, ReplyPacket}; -use crypto::asymmetric::identity; use log::*; +use nym_crypto::asymmetric::identity; +use nym_task::TaskClient; use rand::{thread_rng, Rng}; use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; use std::{fmt, io}; -use task::ShutdownListener; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use tokio::time::sleep; @@ -45,7 +45,7 @@ pub(crate) struct PacketSender { packet_timeout: Duration, connection_timeout: Duration, delay_between_packets: Duration, - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, } impl PacketSender { @@ -55,7 +55,7 @@ impl PacketSender { packet_timeout: Duration, connection_timeout: Duration, delay_between_packets: Duration, - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, ) -> Self { PacketSender { identity, @@ -84,6 +84,7 @@ impl PacketSender { tested_node: TestedNode, ) -> Result { let mut shutdown_listener = self.shutdown_listener.clone(); + shutdown_listener.mark_as_success(); let mut conn = match tokio::time::timeout( self.connection_timeout, diff --git a/common/logging/Cargo.toml b/common/mobile-storage/Cargo.toml similarity index 67% rename from common/logging/Cargo.toml rename to common/mobile-storage/Cargo.toml index 14d2e3a737..98b9d9b42f 100644 --- a/common/logging/Cargo.toml +++ b/common/mobile-storage/Cargo.toml @@ -1,10 +1,11 @@ [package] -name = "logging" +name = "mobile-storage" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -log = "0.4.0" -pretty_env_logger = "0.4.0" \ No newline at end of file +async-trait = { workspace = true } +thiserror = "1.0" + diff --git a/common/mobile-storage/src/lib.rs b/common/mobile-storage/src/lib.rs new file mode 100644 index 0000000000..0de91b0ec4 --- /dev/null +++ b/common/mobile-storage/src/lib.rs @@ -0,0 +1,67 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum StorageError { + #[error("Android is not yet supported")] + AndroidNotSupported, + + #[allow(dead_code)] + #[error("Code shouldn't reach this point")] + InconsistentData, +} + +#[derive(Clone)] +pub struct PersistentStorage {} + +pub struct CoconutCredential { + pub id: i64, + pub voucher_value: String, + pub voucher_info: String, + pub serial_number: String, + pub binding_number: String, + pub signature: String, + pub epoch_id: String, + pub consumed: bool, +} + +#[async_trait] +pub trait Storage: Send + Sync { + async fn insert_coconut_credential( + &self, + voucher_value: String, + voucher_info: String, + serial_number: String, + binding_number: String, + signature: String, + ) -> Result<(), StorageError>; + + async fn get_next_coconut_credential(&self) -> Result; + + async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError>; +} + +#[async_trait] +impl Storage for PersistentStorage { + async fn insert_coconut_credential( + &self, + _voucher_value: String, + _voucher_info: String, + _serial_number: String, + _binding_number: String, + _signature: String, + ) -> Result<(), StorageError> { + Err(StorageError::AndroidNotSupported) + } + + async fn get_next_coconut_credential(&self) -> Result { + Err(StorageError::AndroidNotSupported) + } + + async fn consume_coconut_credential(&self, _id: i64) -> Result<(), StorageError> { + Err(StorageError::AndroidNotSupported) + } +} diff --git a/common/network-defaults/Cargo.toml b/common/network-defaults/Cargo.toml index 0c84938c7b..f2878ad16a 100644 --- a/common/network-defaults/Cargo.toml +++ b/common/network-defaults/Cargo.toml @@ -1,16 +1,17 @@ [package] -name = "network-defaults" +name = "nym-network-defaults" version = "0.1.0" -authors = ["Jędrzej Stuczyński "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "Nym network defaults" +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true [dependencies] -cfg-if = "1.0.0" -dotenv = "0.15.0" +cfg-if = { workspace = true } +dotenvy = { workspace = true } hex-literal = "0.3.3" -once_cell = "1.7.2" -serde = {version = "1.0", features = ["derive"]} -thiserror = "1.0" -url = "2.2" +once_cell = { workspace = true } +serde = { workspace = true, features = ["derive"]} +thiserror = { workspace = true } +url = { workspace = true } diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index d7b01622b1..37e3b8d956 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -1,6 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD}; use serde::{Deserialize, Serialize}; use std::{env::var, ops::Not, path::PathBuf}; use url::Url; @@ -35,6 +36,7 @@ pub struct NymContracts { pub vesting_contract_address: Option, pub bandwidth_claim_contract_address: Option, pub coconut_bandwidth_contract_address: Option, + pub group_contract_address: Option, pub multisig_contract_address: Option, pub coconut_dkg_contract_address: Option, } @@ -77,8 +79,8 @@ impl NymNetworkDetails { .expect("denomination exponent is not u32"), }) .with_validator_endpoint(ValidatorDetails::new( - var(var_names::NYMD_VALIDATOR).expect("nymd validator not set"), - Some(var(var_names::API_VALIDATOR).expect("api validator not set")), + var(var_names::NYXD).expect("nyxd validator not set"), + Some(var(var_names::NYM_API).expect("nym api not set")), )) .with_mixnet_contract(Some( var(var_names::MIXNET_CONTRACT_ADDRESS).expect("mixnet contract not set"), @@ -94,6 +96,9 @@ impl NymNetworkDetails { var(var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS) .expect("coconut bandwidth contract not set"), )) + .with_group_contract(Some( + var(var_names::GROUP_CONTRACT_ADDRESS).expect("group contract not set"), + )) .with_multisig_contract(Some( var(var_names::MULTISIG_CONTRACT_ADDRESS).expect("multisig contract not set"), )) @@ -124,6 +129,7 @@ impl NymNetworkDetails { coconut_bandwidth_contract_address: parse_optional_str( mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, ), + group_contract_address: parse_optional_str(mainnet::GROUP_CONTRACT_ADDRESS), multisig_contract_address: parse_optional_str(mainnet::MULTISIG_CONTRACT_ADDRESS), coconut_dkg_contract_address: parse_optional_str( mainnet::COCONUT_DKG_CONTRACT_ADDRESS, @@ -192,6 +198,12 @@ impl NymNetworkDetails { self } + #[must_use] + pub fn with_group_contract>(mut self, contract: Option) -> Self { + self.contracts.group_contract_address = contract.map(Into::into); + self + } + #[must_use] pub fn with_multisig_contract>(mut self, contract: Option) -> Self { self.contracts.multisig_contract_address = contract.map(Into::into); @@ -254,7 +266,7 @@ impl DenomDetailsOwned { #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct ValidatorDetails { // it is assumed those values are always valid since they're being provided in our defaults file - pub nymd_url: String, + pub nyxd_url: String, // Right now api_url is optional as we are not running the api reliably on all validators // however, later on it should be a mandatory field pub api_url: Option, @@ -262,24 +274,24 @@ pub struct ValidatorDetails { } impl ValidatorDetails { - pub fn new>(nymd_url: S, api_url: Option) -> Self { + pub fn new>(nyxd_url: S, api_url: Option) -> Self { ValidatorDetails { - nymd_url: nymd_url.into(), + nyxd_url: nyxd_url.into(), api_url: api_url.map(Into::into), } } - pub fn new_nymd_only>(nymd_url: S) -> Self { + pub fn new_nyxd_only>(nyxd_url: S) -> Self { ValidatorDetails { - nymd_url: nymd_url.into(), + nyxd_url: nyxd_url.into(), api_url: None, } } - pub fn nymd_url(&self) -> Url { - self.nymd_url + pub fn nyxd_url(&self) -> Url { + self.nyxd_url .parse() - .expect("the provided nymd url is invalid!") + .expect("the provided nyxd url is invalid!") } pub fn api_url(&self) -> Option { @@ -289,13 +301,28 @@ impl ValidatorDetails { } } -pub fn setup_env(config_env_file: Option) { +fn fix_deprecated_environmental_variables() { + // if we're using the outdated environmental variables, set the updated ones to preserve compatibility + if let Ok(nyxd) = std::env::var(DEPRECATED_NYMD_VALIDATOR) { + if std::env::var(NYXD).is_err() { + std::env::set_var(NYXD, nyxd) + } + } + if let Ok(nym_apis) = std::env::var(DEPRECATED_API_VALIDATOR) { + if std::env::var(NYM_API).is_err() { + std::env::set_var(NYM_API, nym_apis) + } + } +} + +pub fn setup_env(config_env_file: Option<&PathBuf>) { match std::env::var(var_names::CONFIGURED) { // if the configuration is not already set in the env vars Err(std::env::VarError::NotPresent) => { if let Some(config_env_file) = config_env_file { - dotenv::from_path(config_env_file) + dotenvy::from_path(config_env_file) .expect("Invalid path to environment configuration file"); + fix_deprecated_environmental_variables(); } else { // if nothing is set, the use mainnet defaults // if the user has not set `CONFIGURED`, then even if they set any of the env variables, @@ -304,7 +331,9 @@ pub fn setup_env(config_env_file: Option) { } } Err(_) => crate::mainnet::export_to_env(), - _ => {} + _ => { + fix_deprecated_environmental_variables(); + } } // if we haven't explicitly defined any of the constants, fallback to defaults @@ -355,14 +384,14 @@ pub const DEFAULT_WEBSOCKET_LISTENING_PORT: u16 = 1977; // 'SOCKS5' CLIENT pub const DEFAULT_SOCKS5_LISTENING_PORT: u16 = 1080; -// VALIDATOR-API -pub const DEFAULT_VALIDATOR_API_PORT: u16 = 8080; +// NYM-API +pub const DEFAULT_NYM_API_PORT: u16 = 8080; -pub const VALIDATOR_API_VERSION: &str = "v1"; +pub const NYM_API_VERSION: &str = "v1"; // REWARDING -/// We'll be assuming a few more things, profit margin and cost function. Since we don't have relialable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms +/// We'll be assuming a few more things, profit margin and cost function. Since we don't have reliable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms // pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$ // pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 55_556; // 40$/1hr at 1 Nym == 1$ // pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 9259; // 40$/1hr/6 at 1 Nym == 1$ diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index ebacd0ff0d..3e37633b3f 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -11,12 +11,13 @@ pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6); pub const MIXNET_CONTRACT_ADDRESS: &str = "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr"; -pub(crate) const VESTING_CONTRACT_ADDRESS: &str = +pub const VESTING_CONTRACT_ADDRESS: &str = "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw"; pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; +pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = @@ -26,17 +27,17 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy"; pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/"; -pub const NYMD_VALIDATOR: &str = "https://rpc.nymtech.net"; -pub const API_VALIDATOR: &str = "https://validator.nymtech.net/api/"; +pub const NYXD_URL: &str = "https://rpc.nymtech.net"; +pub const NYM_API: &str = "https://validator.nymtech.net/api/"; pub(crate) fn validators() -> Vec { - vec![ValidatorDetails::new(NYMD_VALIDATOR, Some(API_VALIDATOR))] + vec![ValidatorDetails::new(NYXD_URL, Some(NYM_API))] } const DEFAULT_SUFFIX: &str = "_MAINNET_DEFAULT"; fn set_var_to_default(var: &str, value: &str) { std::env::set_var(var, value); - std::env::set_var(format!("{}{}", var, DEFAULT_SUFFIX), "1") + std::env::set_var(format!("{var}{DEFAULT_SUFFIX}"), "1") } fn set_var_conditionally_to_default(var: &str, value: &str) { @@ -46,7 +47,7 @@ fn set_var_conditionally_to_default(var: &str, value: &str) { } pub fn uses_default(var: &str) -> bool { - std::env::var(format!("{}{}", var, DEFAULT_SUFFIX)).is_ok() + std::env::var(format!("{var}{DEFAULT_SUFFIX}")).is_ok() } pub fn read_var_if_not_default(var: &str) -> Option { @@ -81,6 +82,7 @@ pub fn export_to_env() { var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, COCONUT_BANDWIDTH_CONTRACT_ADDRESS, ); + set_var_to_default(var_names::GROUP_CONTRACT_ADDRESS, GROUP_CONTRACT_ADDRESS); set_var_to_default( var_names::MULTISIG_CONTRACT_ADDRESS, MULTISIG_CONTRACT_ADDRESS, @@ -97,8 +99,8 @@ pub fn export_to_env() { var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS, STATISTICS_SERVICE_DOMAIN_ADDRESS, ); - set_var_to_default(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR); - set_var_to_default(var_names::API_VALIDATOR, API_VALIDATOR); + set_var_to_default(var_names::NYXD, NYXD_URL); + set_var_to_default(var_names::NYM_API, NYM_API); } pub fn export_to_env_if_not_set() { @@ -125,6 +127,7 @@ pub fn export_to_env_if_not_set() { var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, COCONUT_BANDWIDTH_CONTRACT_ADDRESS, ); + set_var_conditionally_to_default(var_names::GROUP_CONTRACT_ADDRESS, GROUP_CONTRACT_ADDRESS); set_var_conditionally_to_default( var_names::MULTISIG_CONTRACT_ADDRESS, MULTISIG_CONTRACT_ADDRESS, @@ -141,6 +144,6 @@ pub fn export_to_env_if_not_set() { var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS, STATISTICS_SERVICE_DOMAIN_ADDRESS, ); - set_var_conditionally_to_default(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR); - set_var_conditionally_to_default(var_names::API_VALIDATOR, API_VALIDATOR); + set_var_conditionally_to_default(var_names::NYXD, NYXD_URL); + set_var_conditionally_to_default(var_names::NYM_API, NYM_API); } diff --git a/common/network-defaults/src/var_names.rs b/common/network-defaults/src/var_names.rs index 4e62e444cc..0fd5506ddc 100644 --- a/common/network-defaults/src/var_names.rs +++ b/common/network-defaults/src/var_names.rs @@ -14,9 +14,16 @@ pub const MIXNET_CONTRACT_ADDRESS: &str = "MIXNET_CONTRACT_ADDRESS"; pub const VESTING_CONTRACT_ADDRESS: &str = "VESTING_CONTRACT_ADDRESS"; pub const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "BANDWIDTH_CLAIM_CONTRACT_ADDRESS"; pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "COCONUT_BANDWIDTH_CONTRACT_ADDRESS"; +pub const GROUP_CONTRACT_ADDRESS: &str = "GROUP_CONTRACT_ADDRESS"; pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS"; pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS"; pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS"; pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS"; -pub const NYMD_VALIDATOR: &str = "NYMD_VALIDATOR"; -pub const API_VALIDATOR: &str = "API_VALIDATOR"; +pub const NYXD: &str = "NYXD"; +pub const NYM_API: &str = "NYM_API"; + +pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION"; + +// we don't want to explicitly tag those with `#[deprecated]` because then our CI would be red and sad : ( +pub const DEPRECATED_NYMD_VALIDATOR: &str = "NYMD_VALIDATOR"; +pub const DEPRECATED_API_VALIDATOR: &str = "API_VALIDATOR"; diff --git a/common/nonexhaustive-delayqueue/Cargo.toml b/common/nonexhaustive-delayqueue/Cargo.toml index 74c1776e95..975eb631fe 100644 --- a/common/nonexhaustive-delayqueue/Cargo.toml +++ b/common/nonexhaustive-delayqueue/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "nonexhaustive-delayqueue" +name = "nym-nonexhaustive-delayqueue" version = "0.1.0" authors = ["Jędrzej Stuczyński "] edition = "2021" @@ -7,9 +7,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "1.21.2", features = [] } -tokio-stream = "0.1.9" # this one seems to be a thing until `Stream` trait is stabilised in stdlib -tokio-util = { version = "0.7.3", features = ["time"] } +tokio = { version = "1.24.1", features = [] } +tokio-stream = "0.1.11" # this one seems to be a thing until `Stream` trait is stabilised in stdlib +tokio-util = { version = "0.7.4", features = ["time"] } [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-timer] git = "https://github.com/mmsinclair/wasm-timer" @@ -19,4 +19,4 @@ rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6" version = "0.4.4" [target."cfg(target_arch = \"wasm32\")".dependencies.futures-core] -version = "0.3.0" \ No newline at end of file +version = "0.3.0" diff --git a/common/nymcoconut/Cargo.toml b/common/nymcoconut/Cargo.toml index 5dd70dbd2c..ed9cf51b7a 100644 --- a/common/nymcoconut/Cargo.toml +++ b/common/nymcoconut/Cargo.toml @@ -17,8 +17,8 @@ serde_derive = "1.0" bs58 = "0.4.0" sha2 = "0.9" -dkg = { path = "../crypto/dkg" } -pemstore = { path = "../pemstore" } +nym-dkg = { path = "../dkg" } +nym-pemstore = { path = "../pemstore" } [dependencies.ff] version = "0.11" @@ -29,13 +29,10 @@ version = "0.11" default-features = false [dev-dependencies] -criterion = { version="0.3", features=["html_reports"] } +criterion = { version="0.4", features=["html_reports"] } doc-comment = "0.3" rand_chacha = "0.3" -[dev-dependencies.bincode] -version = "1" - [[bench]] name = "benchmarks" harness = false diff --git a/common/nymcoconut/src/scheme/keygen.rs b/common/nymcoconut/src/scheme/keygen.rs index ea9c88aa65..352d704193 100644 --- a/common/nymcoconut/src/scheme/keygen.rs +++ b/common/nymcoconut/src/scheme/keygen.rs @@ -9,7 +9,7 @@ use std::convert::TryInto; use bls12_381::{G1Projective, G2Projective, Scalar}; use group::Curve; -use pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use serde_derive::{Deserialize, Serialize}; use crate::error::{CoconutError, Result}; @@ -68,8 +68,7 @@ impl TryFrom<&[u8]> for SecretKey { if ys_len as usize != actual_ys_len { return Err(CoconutError::Deserialization(format!( - "Tried to deserialize secret key with inconsistent ys len (expected {}, got {})", - ys_len, actual_ys_len + "Tried to deserialize secret key with inconsistent ys len (expected {ys_len}, got {actual_ys_len})" ))); } @@ -94,6 +93,10 @@ impl SecretKey { Self { x, ys } } + pub fn into_raw(&self) -> (Scalar, Vec) { + (self.x, self.ys.clone()) + } + /// Derive verification key using this secret key. pub fn verification_key(&self, params: &Parameters) -> VerificationKey { let g1 = params.gen1(); @@ -185,8 +188,7 @@ impl TryFrom<&[u8]> for VerificationKey { if betas_len as usize != actual_betas_len { return Err( CoconutError::Deserialization( - format!("Tried to deserialize verification key with inconsistent betas len (expected {}, got {})", - betas_len, actual_betas_len + format!("Tried to deserialize verification key with inconsistent betas len (expected {betas_len}, got {actual_betas_len})" ))); } diff --git a/common/nymcoconut/src/scheme/setup.rs b/common/nymcoconut/src/scheme/setup.rs index a328ede31c..926d8f5ed0 100644 --- a/common/nymcoconut/src/scheme/setup.rs +++ b/common/nymcoconut/src/scheme/setup.rs @@ -33,7 +33,7 @@ impl Parameters { } let hs = (1..=num_attributes) - .map(|i| hash_g1(format!("h{}", i)).to_affine()) + .map(|i| hash_g1(format!("h{i}")).to_affine()) .collect(); Ok(Parameters { diff --git a/common/nymcoconut/src/tests/helpers.rs b/common/nymcoconut/src/tests/helpers.rs index ee7e418788..a432cecd60 100644 --- a/common/nymcoconut/src/tests/helpers.rs +++ b/common/nymcoconut/src/tests/helpers.rs @@ -89,6 +89,9 @@ pub fn theta_from_keys_and_attributes( } pub fn transpose_matrix(matrix: Vec>) -> Vec> { + if matrix.is_empty() { + return vec![]; + } let len = matrix[0].len(); let mut iters: Vec<_> = matrix.into_iter().map(|d| d.into_iter()).collect(); (0..len) @@ -106,13 +109,13 @@ pub mod tests { use super::*; use crate::{KeyPair, Parameters, SecretKey}; use bls12_381::Scalar; - use dkg::{bte::decrypt_share, combine_shares, Dealing, NodeIndex}; + use nym_dkg::{bte::decrypt_share, combine_shares, Dealing, NodeIndex}; use rand_chacha::rand_core::SeedableRng; pub fn generate_dkg_secrets(node_indices: &[NodeIndex]) -> Vec { let dummy_seed = [42u8; 32]; let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed); - let params = dkg::bte::setup(); + let params = nym_dkg::bte::setup(); // the simplest possible case let threshold = 2; @@ -120,7 +123,7 @@ pub mod tests { let mut receivers = std::collections::BTreeMap::new(); let mut full_keys = Vec::new(); for index in node_indices { - let (dk, pk) = dkg::bte::keygen(¶ms, &mut rng); + let (dk, pk) = nym_dkg::bte::keygen(¶ms, &mut rng); receivers.insert(*index, *pk.public_key()); full_keys.push((dk, pk)) } diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index d9d81aecd4..c4d53a70a5 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -1,38 +1,39 @@ [package] -name = "nymsphinx" +name = "nym-sphinx" version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "Top-level crate for sphinx packets as used by the Nym mixnet" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] rand = { version = "0.7.3", features = ["wasm-bindgen"] } rand_distr = "0.3" thiserror = "1.0.37" -nymsphinx-acknowledgements = { path = "acknowledgements" } -nymsphinx-addressing = { path = "addressing" } -nymsphinx-anonymous-replies = { path = "anonymous-replies" } -nymsphinx-chunking = { path = "chunking" } -nymsphinx-cover = { path = "cover" } -nymsphinx-forwarding = { path = "forwarding" } -nymsphinx-params = { path = "params" } -nymsphinx-types = { path = "types" } +nym-sphinx-acknowledgements = { path = "acknowledgements" } +nym-sphinx-addressing = { path = "addressing" } +nym-sphinx-anonymous-replies = { path = "anonymous-replies" } +nym-sphinx-chunking = { path = "chunking" } +nym-sphinx-cover = { path = "cover" } +nym-sphinx-forwarding = { path = "forwarding" } +nym-sphinx-params = { path = "params" } +nym-sphinx-types = { path = "types" } # those dependencies are due to intriducing preparer and receiver. Perpaphs that indicates they should be moved # to separate crate? -crypto = { path = "../crypto" } -topology = { path = "../topology" } +nym-crypto = { path = "../crypto", version = "0.2.0" } +nym-topology = { path = "../topology" } [dev-dependencies] -mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } +nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } # do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require # net2 via tokio-util -> tokio -> mio -> net2 -[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nymsphinx-framing] +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-sphinx-framing] path = "framing" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] -version = "1.21.2" +version = "1.24.1" features = ["sync"] diff --git a/common/nymsphinx/acknowledgements/Cargo.toml b/common/nymsphinx/acknowledgements/Cargo.toml index f105e0c544..3db7d909db 100644 --- a/common/nymsphinx/acknowledgements/Cargo.toml +++ b/common/nymsphinx/acknowledgements/Cargo.toml @@ -1,19 +1,21 @@ [package] -name = "nymsphinx-acknowledgements" +name = "nym-sphinx-acknowledgements" version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "Sphinx packet ack messages" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] rand = { version = "0.7.3", features = ["wasm-bindgen"] } +thiserror = "1.0.37" -crypto = { path = "../../crypto", features = ["symmetric", "rand"] } -nymsphinx-addressing = { path = "../addressing" } -nymsphinx-params = { path = "../params" } -nymsphinx-types = { path = "../types" } -pemstore = { path = "../../pemstore" } -topology = { path = "../../topology" } +nym-crypto = { path = "../../crypto", features = ["symmetric", "rand"] } +nym-sphinx-addressing = { path = "../addressing" } +nym-sphinx-params = { path = "../params" } +nym-sphinx-types = { path = "../types" } +nym-pemstore = { path = "../../pemstore" } +nym-topology = { path = "../../topology" } diff --git a/common/nymsphinx/acknowledgements/src/identifier.rs b/common/nymsphinx/acknowledgements/src/identifier.rs index 7b7a2ae844..9f6ffb49be 100644 --- a/common/nymsphinx/acknowledgements/src/identifier.rs +++ b/common/nymsphinx/acknowledgements/src/identifier.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::AckKey; -use crypto::symmetric::stream_cipher::{self, encrypt, iv_from_slice, random_iv, IvSizeUser}; -use nymsphinx_params::{ +use nym_crypto::symmetric::stream_cipher::{self, encrypt, iv_from_slice, random_iv, IvSizeUser}; +use nym_sphinx_params::{ packet_sizes::PacketSize, AckEncryptionAlgorithm, SerializedFragmentIdentifier, FRAG_ID_LEN, }; use rand::{CryptoRng, RngCore}; diff --git a/common/nymsphinx/acknowledgements/src/key.rs b/common/nymsphinx/acknowledgements/src/key.rs index ec65537c87..e43a4cf6e5 100644 --- a/common/nymsphinx/acknowledgements/src/key.rs +++ b/common/nymsphinx/acknowledgements/src/key.rs @@ -1,9 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crypto::symmetric::stream_cipher::{generate_key, CipherKey, KeySizeUser}; -use nymsphinx_params::AckEncryptionAlgorithm; -use pemstore::traits::PemStorableKey; +use nym_crypto::symmetric::stream_cipher::{generate_key, CipherKey, KeySizeUser}; +use nym_pemstore::traits::PemStorableKey; +use nym_sphinx_params::AckEncryptionAlgorithm; use rand::{CryptoRng, RngCore}; use std::fmt::{self, Display, Formatter}; diff --git a/common/nymsphinx/acknowledgements/src/surb_ack.rs b/common/nymsphinx/acknowledgements/src/surb_ack.rs index 67e863d1f6..5df36e1f0b 100644 --- a/common/nymsphinx/acknowledgements/src/surb_ack.rs +++ b/common/nymsphinx/acknowledgements/src/surb_ack.rs @@ -3,19 +3,23 @@ use crate::identifier::prepare_identifier; use crate::AckKey; -use nymsphinx_addressing::clients::Recipient; -use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN}; -use nymsphinx_params::packet_sizes::PacketSize; -use nymsphinx_params::DEFAULT_NUM_MIX_HOPS; -use nymsphinx_types::builder::SphinxPacketBuilder; -use nymsphinx_types::{ +use nym_sphinx_addressing::clients::Recipient; +use nym_sphinx_addressing::nodes::{ + NymNodeRoutingAddress, NymNodeRoutingAddressError, MAX_NODE_ADDRESS_UNPADDED_LEN, +}; +use nym_sphinx_params::packet_sizes::PacketSize; +use nym_sphinx_params::DEFAULT_NUM_MIX_HOPS; +use nym_sphinx_types::builder::SphinxPacketBuilder; +use nym_sphinx_types::Error as SphinxError; +use nym_sphinx_types::{ delays::{self, Delay}, SphinxPacket, }; +use nym_topology::{NymTopology, NymTopologyError}; use rand::{CryptoRng, RngCore}; use std::convert::TryFrom; use std::time; -use topology::{NymTopology, NymTopologyError}; +use thiserror::Error; pub struct SurbAck { surb_ack_packet: SphinxPacket, @@ -23,11 +27,16 @@ pub struct SurbAck { expected_total_delay: Delay, } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum SurbAckRecoveryError { - InvalidPacketSize, - InvalidAddress, - InvalidSphinxPacket, + #[error("received an invalid number of bytes to deserialize the SURB-Ack. Got {received}, expected {expected}")] + InvalidPacketSize { received: usize, expected: usize }, + + #[error("could not extract first hop address information - {0}")] + InvalidAddress(#[from] NymNodeRoutingAddressError), + + #[error("the contained sphinx packet was not correctly formed - {0}")] + InvalidSphinxPacket(#[from] SphinxError), } impl SurbAck { @@ -92,20 +101,17 @@ impl SurbAck { b: &[u8], ) -> Result<(NymNodeRoutingAddress, SphinxPacket), SurbAckRecoveryError> { if b.len() != Self::len() { - Err(SurbAckRecoveryError::InvalidPacketSize) + Err(SurbAckRecoveryError::InvalidPacketSize { + received: b.len(), + expected: Self::len(), + }) } else { - let address = match NymNodeRoutingAddress::try_from_bytes(b) { - Ok(address) => address, - Err(_) => return Err(SurbAckRecoveryError::InvalidAddress), - }; + let address = NymNodeRoutingAddress::try_from_bytes(b)?; // TODO: this will be variable once/if we decide to introduce optimization described // in common/nymsphinx/chunking/src/lib.rs:available_plaintext_size() let address_offset = MAX_NODE_ADDRESS_UNPADDED_LEN; - let packet = match SphinxPacket::from_bytes(&b[address_offset..]) { - Ok(packet) => packet, - Err(_) => return Err(SurbAckRecoveryError::InvalidSphinxPacket), - }; + let packet = SphinxPacket::from_bytes(&b[address_offset..])?; Ok((address, packet)) } diff --git a/common/nymsphinx/addressing/Cargo.toml b/common/nymsphinx/addressing/Cargo.toml index 7a85486e4f..f9c646e04b 100644 --- a/common/nymsphinx/addressing/Cargo.toml +++ b/common/nymsphinx/addressing/Cargo.toml @@ -1,14 +1,15 @@ [package] -name = "nymsphinx-addressing" +name = "nym-sphinx-addressing" version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "Nym mixnet addressing" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] -crypto = { path = "../../crypto", features = ["asymmetric"] } # all addresses are expressed in terms on their crypto keys -nymsphinx-types = { path = "../types" } # we need to be able to refer to some types defined inside sphinx crate +nym-crypto = { path = "../../crypto", features = ["asymmetric"] } # all addresses are expressed in terms on their crypto keys +nym-sphinx-types = { path = "../types" } # we need to be able to refer to some types defined inside sphinx crate serde = "1.0" # implementing serialization/deserialization for some types, like `Recipient` thiserror = "1.0.37" diff --git a/common/nymsphinx/addressing/src/clients.rs b/common/nymsphinx/addressing/src/clients.rs index fd7235a9b7..a37f15a5fa 100644 --- a/common/nymsphinx/addressing/src/clients.rs +++ b/common/nymsphinx/addressing/src/clients.rs @@ -5,11 +5,12 @@ // it's already destructed). use crate::nodes::{NodeIdentity, NODE_IDENTITY_SIZE}; -use crypto::asymmetric::{encryption, identity}; -use nymsphinx_types::Destination; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_sphinx_types::Destination; use serde::de::{Error as SerdeError, Unexpected, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt::{self, Formatter}; +use std::str::FromStr; use thiserror::Error; // Not entirely sure whether this is the correct place for those, but let's see how it's going @@ -225,6 +226,14 @@ impl std::fmt::Display for Recipient { } } +impl FromStr for Recipient { + type Err = RecipientFormattingError; + + fn from_str(s: &str) -> Result { + Recipient::try_from_base58_string(s) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/common/nymsphinx/addressing/src/nodes.rs b/common/nymsphinx/addressing/src/nodes.rs index cf43625c5a..d1ed9d1790 100644 --- a/common/nymsphinx/addressing/src/nodes.rs +++ b/common/nymsphinx/addressing/src/nodes.rs @@ -1,8 +1,8 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crypto::asymmetric::identity; -use nymsphinx_types::{NodeAddressBytes, NODE_ADDRESS_LENGTH}; +use nym_crypto::asymmetric::identity; +use nym_sphinx_types::{NodeAddressBytes, NODE_ADDRESS_LENGTH}; use std::convert::{TryFrom, TryInto}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use thiserror::Error; diff --git a/common/nymsphinx/anonymous-replies/Cargo.toml b/common/nymsphinx/anonymous-replies/Cargo.toml index 5ddb069e19..40a6d19308 100644 --- a/common/nymsphinx/anonymous-replies/Cargo.toml +++ b/common/nymsphinx/anonymous-replies/Cargo.toml @@ -1,10 +1,11 @@ [package] -name = "nymsphinx-anonymous-replies" +name = "nym-sphinx-anonymous-replies" version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "Anonymous sphinx packet replies using singly-use-reply-blocks (SURB)" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] rand = { version = "0.7.3", features = ["wasm-bindgen"] } @@ -12,11 +13,11 @@ bs58 = "0.4" serde = "1.0" thiserror = "1" -crypto = { path = "../../crypto" } -nymsphinx-addressing = { path = "../addressing" } -nymsphinx-params = { path = "../params" } -nymsphinx-types = { path = "../types" } -topology = { path = "../../topology" } +nym-crypto = { path = "../../crypto", features = ["symmetric", "rand"] } +nym-sphinx-addressing = { path = "../addressing" } +nym-sphinx-params = { path = "../params" } +nym-sphinx-types = { path = "../types" } +nym-topology = { path = "../../topology" } [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen] -version = "0.2.83" \ No newline at end of file +version = "0.2.83" diff --git a/common/nymsphinx/anonymous-replies/src/encryption_key.rs b/common/nymsphinx/anonymous-replies/src/encryption_key.rs index 7a6689f4a7..87d847902b 100644 --- a/common/nymsphinx/anonymous-replies/src/encryption_key.rs +++ b/common/nymsphinx/anonymous-replies/src/encryption_key.rs @@ -1,13 +1,13 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crypto::{ +use nym_crypto::{ crypto_hash, generic_array::{typenum::Unsigned, GenericArray}, symmetric::stream_cipher::{generate_key, CipherKey, KeySizeUser}, OutputSizeUser, }; -use nymsphinx_params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm}; +use nym_sphinx_params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm}; use rand::{CryptoRng, RngCore}; use std::fmt::{self, Display, Formatter}; diff --git a/common/nymsphinx/anonymous-replies/src/reply_surb.rs b/common/nymsphinx/anonymous-replies/src/reply_surb.rs index 1fb29b2910..4f00cdc164 100644 --- a/common/nymsphinx/anonymous-replies/src/reply_surb.rs +++ b/common/nymsphinx/anonymous-replies/src/reply_surb.rs @@ -2,12 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use crate::encryption_key::{SurbEncryptionKey, SurbEncryptionKeyError, SurbEncryptionKeySize}; -use crypto::{generic_array::typenum::Unsigned, Digest}; -use nymsphinx_addressing::clients::Recipient; -use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN}; -use nymsphinx_params::packet_sizes::PacketSize; -use nymsphinx_params::{ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS}; -use nymsphinx_types::{delays, Error as SphinxError, SURBMaterial, SphinxPacket, SURB}; +use nym_crypto::{generic_array::typenum::Unsigned, Digest}; +use nym_sphinx_addressing::clients::Recipient; +use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN}; +use nym_sphinx_params::packet_sizes::PacketSize; +use nym_sphinx_params::{ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS}; +use nym_sphinx_types::{delays, Error as SphinxError, SURBMaterial, SphinxPacket, SURB}; +use nym_topology::{NymTopology, NymTopologyError}; use rand::{CryptoRng, RngCore}; use serde::de::{Error as SerdeError, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -15,7 +16,6 @@ use std::convert::TryFrom; use std::fmt::{self, Formatter}; use std::time; use thiserror::Error; -use topology::{NymTopology, NymTopologyError}; #[derive(Debug, Error)] pub enum ReplySurbError { @@ -111,7 +111,7 @@ impl ReplySurb { /// Returns the expected number of bytes the [`ReplySURB`] will take after serialization. /// Useful for deserialization from a bytes stream. pub fn serialized_len(mix_hops: u8) -> usize { - use nymsphinx_types::{HEADER_SIZE, NODE_ADDRESS_LENGTH, PAYLOAD_KEY_SIZE}; + use nym_sphinx_types::{HEADER_SIZE, NODE_ADDRESS_LENGTH, PAYLOAD_KEY_SIZE}; // the SURB itself consists of SURB_header, first hop address and set of payload keys // (note extra 1 for the gateway) diff --git a/common/nymsphinx/anonymous-replies/src/requests.rs b/common/nymsphinx/anonymous-replies/src/requests.rs index 686f65aa79..a812474632 100644 --- a/common/nymsphinx/anonymous-replies/src/requests.rs +++ b/common/nymsphinx/anonymous-replies/src/requests.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ReplySurb, ReplySurbError}; -use nymsphinx_addressing::clients::{Recipient, RecipientFormattingError}; +use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError}; use rand::{CryptoRng, RngCore}; use std::fmt::{Display, Formatter}; use std::mem; diff --git a/common/nymsphinx/chunking/Cargo.toml b/common/nymsphinx/chunking/Cargo.toml index 3d6bacab61..94bec5886b 100644 --- a/common/nymsphinx/chunking/Cargo.toml +++ b/common/nymsphinx/chunking/Cargo.toml @@ -1,16 +1,19 @@ [package] -name = "nymsphinx-chunking" +name = "nym-sphinx-chunking" version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" +description = "Sphinx packet chunking of underlying data packets" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -log = "0.4.8" +log = { workspace = true } rand = { version = "0.7.3", features = ["wasm-bindgen"] } thiserror = "1.0.37" -nymsphinx-addressing = { path = "../addressing" } -nymsphinx-params = { path = "../params" } -nymsphinx-types = { path = "../types" } +nym-sphinx-addressing = { path = "../addressing" } +nym-sphinx-params = { path = "../params" } +nym-sphinx-types = { path = "../types" } diff --git a/common/nymsphinx/chunking/src/fragment.rs b/common/nymsphinx/chunking/src/fragment.rs index 3f102282dd..7cc2d2c0e7 100644 --- a/common/nymsphinx/chunking/src/fragment.rs +++ b/common/nymsphinx/chunking/src/fragment.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::ChunkingError; -use nymsphinx_params::{SerializedFragmentIdentifier, FRAG_ID_LEN}; +use nym_sphinx_params::{SerializedFragmentIdentifier, FRAG_ID_LEN}; use std::convert::TryInto; use std::fmt::{self, Debug, Formatter}; @@ -208,6 +208,11 @@ impl Fragment { } } + /// Gets the size of payload contained in this `Fragment`. + pub fn payload_size(&self) -> usize { + self.payload.len() + } + /// Extracts id of this `Fragment`. pub fn id(&self) -> i32 { self.header.id @@ -456,7 +461,7 @@ impl FragmentHeader { #[cfg(test)] mod fragment_tests { use super::*; - use nymsphinx_params::packet_sizes::PacketSize; + use nym_sphinx_params::packet_sizes::PacketSize; use rand::{thread_rng, RngCore}; fn max_plaintext_size() -> usize { diff --git a/common/nymsphinx/chunking/src/lib.rs b/common/nymsphinx/chunking/src/lib.rs index 60316edb15..673181f773 100644 --- a/common/nymsphinx/chunking/src/lib.rs +++ b/common/nymsphinx/chunking/src/lib.rs @@ -137,8 +137,8 @@ pub fn number_of_required_fragments( mod tests { use super::*; use crate::set::{max_one_way_linked_set_payload_length, two_way_linked_set_payload_length}; - use nymsphinx_addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN; - use nymsphinx_params::packet_sizes::PacketSize; + use nym_sphinx_addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN; + use nym_sphinx_params::packet_sizes::PacketSize; #[test] fn calculating_number_of_required_fragments() { diff --git a/common/nymsphinx/chunking/src/set.rs b/common/nymsphinx/chunking/src/set.rs index 47e6b947c6..be5ae739da 100644 --- a/common/nymsphinx/chunking/src/set.rs +++ b/common/nymsphinx/chunking/src/set.rs @@ -66,13 +66,15 @@ pub(crate) type FragmentSet = Vec; /// `Fragment`s thus allowing for some additional optimizations by letting it skip /// certain procedures when reconstructing. pub(crate) fn generate_set_id(rng: &mut R) -> i32 { - let potential_id = rng.gen::().abs(); + let potential_id = rng.gen::(); // make sure id is always non-zero, as we do not want to accidentally have weird // reconstruction cases where unfragmented payload overwrites some part of set with id0 - if potential_id == 0 { + // furthermore, make sure it's not i32::MIN (-2147483648) as due to 2-complement encoding, + // attempting to calculate the absolutely value is going to panic + if potential_id == 0 || potential_id == i32::MIN { generate_set_id(rng) } else { - potential_id + potential_id.abs() } } @@ -297,7 +299,7 @@ pub fn split_into_sets( #[cfg(test)] mod tests { use super::*; - use nymsphinx_params::packet_sizes::PacketSize; + use nym_sphinx_params::packet_sizes::PacketSize; fn max_plaintext_size() -> usize { PacketSize::default().plaintext_size() - PacketSize::AckPacket.size() diff --git a/common/nymsphinx/cover/Cargo.toml b/common/nymsphinx/cover/Cargo.toml index 064ebf1298..f1558d3676 100644 --- a/common/nymsphinx/cover/Cargo.toml +++ b/common/nymsphinx/cover/Cargo.toml @@ -1,20 +1,21 @@ [package] -name = "nymsphinx-cover" +name = "nym-sphinx-cover" version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "Sphinx packets used as cover traffic" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] rand = { version = "0.7.3", features = ["wasm-bindgen"] } thiserror = "1.0.37" -crypto = { path = "../../crypto" } -nymsphinx-acknowledgements = { path = "../acknowledgements" } -nymsphinx-addressing = { path = "../addressing" } -nymsphinx-chunking = { path = "../chunking" } -nymsphinx-params = { path = "../params" } -nymsphinx-forwarding = { path = "../forwarding" } -nymsphinx-types = { path = "../types" } -topology = { path = "../../topology" } +nym-crypto = { path = "../../crypto" } +nym-sphinx-acknowledgements = { path = "../acknowledgements" } +nym-sphinx-addressing = { path = "../addressing" } +nym-sphinx-chunking = { path = "../chunking" } +nym-sphinx-params = { path = "../params" } +nym-sphinx-forwarding = { path = "../forwarding" } +nym-sphinx-types = { path = "../types" } +nym-topology = { path = "../../topology" } diff --git a/common/nymsphinx/cover/src/lib.rs b/common/nymsphinx/cover/src/lib.rs index 2ce9454d5c..19e6e96e09 100644 --- a/common/nymsphinx/cover/src/lib.rs +++ b/common/nymsphinx/cover/src/lib.rs @@ -1,25 +1,25 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crypto::shared_key::new_ephemeral_shared_key; -use crypto::symmetric::stream_cipher; -use nymsphinx_acknowledgements::surb_ack::SurbAck; -use nymsphinx_acknowledgements::AckKey; -use nymsphinx_addressing::clients::Recipient; -use nymsphinx_addressing::nodes::NymNodeRoutingAddress; -use nymsphinx_chunking::fragment::COVER_FRAG_ID; -use nymsphinx_forwarding::packet::MixPacket; -use nymsphinx_params::packet_sizes::PacketSize; -use nymsphinx_params::{ +use nym_crypto::shared_key::new_ephemeral_shared_key; +use nym_crypto::symmetric::stream_cipher; +use nym_sphinx_acknowledgements::surb_ack::SurbAck; +use nym_sphinx_acknowledgements::AckKey; +use nym_sphinx_addressing::clients::Recipient; +use nym_sphinx_addressing::nodes::NymNodeRoutingAddress; +use nym_sphinx_chunking::fragment::COVER_FRAG_ID; +use nym_sphinx_forwarding::packet::MixPacket; +use nym_sphinx_params::packet_sizes::PacketSize; +use nym_sphinx_params::{ PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketMode, DEFAULT_NUM_MIX_HOPS, }; -use nymsphinx_types::builder::SphinxPacketBuilder; -use nymsphinx_types::{delays, Error as SphinxError}; +use nym_sphinx_types::builder::SphinxPacketBuilder; +use nym_sphinx_types::{delays, Error as SphinxError}; +use nym_topology::{NymTopology, NymTopologyError}; use rand::{CryptoRng, RngCore}; use std::convert::TryFrom; use std::time; use thiserror::Error; -use topology::{NymTopology, NymTopologyError}; pub const LOOP_COVER_MESSAGE_PAYLOAD: &[u8] = b"The cake is a lie!"; diff --git a/common/nymsphinx/forwarding/Cargo.toml b/common/nymsphinx/forwarding/Cargo.toml index 6b29d9f9e5..e4931b7d8b 100644 --- a/common/nymsphinx/forwarding/Cargo.toml +++ b/common/nymsphinx/forwarding/Cargo.toml @@ -1,12 +1,13 @@ [package] -name = "nymsphinx-forwarding" +name = "nym-sphinx-forwarding" version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "Sphinx packet forwarding as Nym mix packets" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] -nymsphinx-addressing = { path = "../addressing" } -nymsphinx-params = { path = "../params" } -nymsphinx-types = { path = "../types" } +nym-sphinx-addressing = { path = "../addressing" } +nym-sphinx-params = { path = "../params" } +nym-sphinx-types = { path = "../types" } diff --git a/common/nymsphinx/forwarding/src/packet.rs b/common/nymsphinx/forwarding/src/packet.rs index 8c22b43f5c..024b80b005 100644 --- a/common/nymsphinx/forwarding/src/packet.rs +++ b/common/nymsphinx/forwarding/src/packet.rs @@ -1,9 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; -use nymsphinx_params::{PacketMode, PacketSize}; -use nymsphinx_types::SphinxPacket; +use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; +use nym_sphinx_params::{PacketMode, PacketSize}; +use nym_sphinx_types::SphinxPacket; use std::convert::TryFrom; use std::fmt::{self, Debug, Display, Formatter}; diff --git a/common/nymsphinx/framing/Cargo.toml b/common/nymsphinx/framing/Cargo.toml index 6921a8cf69..b510926555 100644 --- a/common/nymsphinx/framing/Cargo.toml +++ b/common/nymsphinx/framing/Cargo.toml @@ -1,14 +1,16 @@ [package] -name = "nymsphinx-framing" +name = "nym-sphinx-framing" version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "Sphinx packet framing for the Nym mixnet" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] bytes = "1.0" -tokio-util = { version = "0.7.3", features = ["codec"] } +tokio-util = { version = "0.7.4", features = ["codec"] } +thiserror = "1.0.37" -nymsphinx-types = { path = "../types" } -nymsphinx-params = { path = "../params" } +nym-sphinx-types = { path = "../types" } +nym-sphinx-params = { path = "../params" } diff --git a/common/nymsphinx/framing/src/codec.rs b/common/nymsphinx/framing/src/codec.rs index 5794a88544..1f427dc0d1 100644 --- a/common/nymsphinx/framing/src/codec.rs +++ b/common/nymsphinx/framing/src/codec.rs @@ -1,57 +1,48 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::packet::{FramedSphinxPacket, Header}; use bytes::{Buf, BufMut, BytesMut}; -use nymsphinx_params::packet_modes::InvalidPacketMode; -use nymsphinx_params::packet_sizes::{InvalidPacketSize, PacketSize}; -use nymsphinx_types::SphinxPacket; +use nym_sphinx_params::packet_modes::InvalidPacketMode; +use nym_sphinx_params::packet_sizes::{InvalidPacketSize, PacketSize}; +use nym_sphinx_types::Error as SphinxError; +use nym_sphinx_types::SphinxPacket; use std::io; +use thiserror::Error; use tokio_util::codec::{Decoder, Encoder}; -#[derive(Debug)] +#[derive(Error, Debug)] pub enum SphinxCodecError { - InvalidPacketSize, - InvalidPacketMode, - MalformedSphinxPacket, - IoError(io::Error), -} + #[error("the packet size information was malformed - {0}")] + InvalidPacketSize(#[from] InvalidPacketSize), -impl From for SphinxCodecError { - fn from(err: io::Error) -> Self { - SphinxCodecError::IoError(err) - } + #[error("the packet mode information was malformed - {0}")] + InvalidPacketMode(#[from] InvalidPacketMode), + + #[error("the actual sphinx packet was malformed - {0}")] + MalformedSphinxPacket(#[from] SphinxError), + + #[error("encountered an IO error - {0}")] + IoError(#[from] io::Error), } impl From for io::Error { fn from(err: SphinxCodecError) -> Self { match err { - SphinxCodecError::InvalidPacketSize => { - io::Error::new(io::ErrorKind::InvalidInput, "invalid packet size") + SphinxCodecError::InvalidPacketSize(source) => { + io::Error::new(io::ErrorKind::InvalidInput, source) } - SphinxCodecError::InvalidPacketMode => { - io::Error::new(io::ErrorKind::InvalidInput, "invalid packet mode") + SphinxCodecError::InvalidPacketMode(source) => { + io::Error::new(io::ErrorKind::InvalidInput, source) } - SphinxCodecError::MalformedSphinxPacket => { - io::Error::new(io::ErrorKind::InvalidData, "malformed packet") + SphinxCodecError::MalformedSphinxPacket(source) => { + io::Error::new(io::ErrorKind::InvalidData, source) } SphinxCodecError::IoError(err) => err, } } } -impl From for SphinxCodecError { - fn from(_: InvalidPacketSize) -> Self { - SphinxCodecError::InvalidPacketSize - } -} - -impl From for SphinxCodecError { - fn from(_: InvalidPacketMode) -> Self { - SphinxCodecError::InvalidPacketMode - } -} - // TODO: in the future it could be extended to have state containing symmetric encryption key // so that all data could be encrypted easily (alternatively we could just slap TLS) pub struct SphinxCodec; @@ -97,17 +88,11 @@ impl Decoder for SphinxCodec { // advance buffer past the header - at this point we have enough bytes src.advance(header.size()); let sphinx_packet_bytes = src.split_to(sphinx_packet_size); - let sphinx_packet = match SphinxPacket::from_bytes(&sphinx_packet_bytes) { - Ok(sphinx_packet) => sphinx_packet, - // here it could be debatable whether stream is corrupt or not, - // but let's go with the safer approach and assume it is. - Err(_) => return Err(SphinxCodecError::MalformedSphinxPacket), - }; - let nymsphinx_packet = FramedSphinxPacket { - header, - packet: sphinx_packet, - }; + // here it could be debatable whether stream is corrupt or not, + // but let's go with the safer approach and assume it is. + let packet = SphinxPacket::from_bytes(&sphinx_packet_bytes)?; + let nymsphinx_packet = FramedSphinxPacket { header, packet }; // As per docs: // Before returning from the function, implementations should ensure that the buffer @@ -143,8 +128,8 @@ impl Decoder for SphinxCodec { #[cfg(test)] mod packet_encoding { use super::*; - use nymsphinx_types::builder::SphinxPacketBuilder; - use nymsphinx_types::{ + use nym_sphinx_types::builder::SphinxPacketBuilder; + use nym_sphinx_types::{ crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH, }; @@ -204,8 +189,8 @@ mod packet_encoding { #[cfg(test)] mod decode_will_allocate_enough_bytes_for_next_call { use super::*; - use nymsphinx_params::packet_version::PacketVersion; - use nymsphinx_params::PacketMode; + use nym_sphinx_params::packet_version::PacketVersion; + use nym_sphinx_params::PacketMode; #[test] fn for_empty_bytes() { diff --git a/common/nymsphinx/framing/src/packet.rs b/common/nymsphinx/framing/src/packet.rs index 997f2e6c35..e001083d31 100644 --- a/common/nymsphinx/framing/src/packet.rs +++ b/common/nymsphinx/framing/src/packet.rs @@ -3,10 +3,10 @@ use crate::codec::SphinxCodecError; use bytes::{BufMut, BytesMut}; -use nymsphinx_params::packet_sizes::PacketSize; -use nymsphinx_params::packet_version::PacketVersion; -use nymsphinx_params::PacketMode; -use nymsphinx_types::SphinxPacket; +use nym_sphinx_params::packet_sizes::PacketSize; +use nym_sphinx_params::packet_version::PacketVersion; +use nym_sphinx_params::PacketMode; +use nym_sphinx_types::SphinxPacket; use std::convert::TryFrom; pub struct FramedSphinxPacket { diff --git a/common/nymsphinx/params/Cargo.toml b/common/nymsphinx/params/Cargo.toml index a06f897f22..6037bdb333 100644 --- a/common/nymsphinx/params/Cargo.toml +++ b/common/nymsphinx/params/Cargo.toml @@ -1,11 +1,14 @@ [package] -name = "nymsphinx-params" +name = "nym-sphinx-params" version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "Sphinx packet parameters for the Nym mixnet" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] -crypto = { path = "../../crypto", features = ["hashing", "symmetric"] } -nymsphinx-types = { path = "../types" } +thiserror = "1.0.37" + +nym-crypto = { path = "../../crypto", features = ["hashing", "symmetric"] } +nym-sphinx-types = { path = "../types" } diff --git a/common/nymsphinx/params/src/lib.rs b/common/nymsphinx/params/src/lib.rs index d954163861..5df4db1a2a 100644 --- a/common/nymsphinx/params/src/lib.rs +++ b/common/nymsphinx/params/src/lib.rs @@ -1,9 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crypto::aes::Aes128; -use crypto::blake3; -use crypto::ctr; +use nym_crypto::aes::Aes128; +use nym_crypto::blake3; +use nym_crypto::ctr; type Aes128Ctr = ctr::Ctr64BE; diff --git a/common/nymsphinx/params/src/packet_modes.rs b/common/nymsphinx/params/src/packet_modes.rs index 819b32b3af..87a0c80caf 100644 --- a/common/nymsphinx/params/src/packet_modes.rs +++ b/common/nymsphinx/params/src/packet_modes.rs @@ -1,16 +1,21 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use std::convert::TryFrom; +use thiserror::Error; -#[derive(Debug)] -pub struct InvalidPacketMode; +#[derive(Error, Debug)] +#[error("{received} is not a valid packet mode tag")] +pub struct InvalidPacketMode { + received: u8, +} #[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum PacketMode { /// Represents 'normal' packet sent through the network that should be delayed by an appropriate /// value at each hop. + #[default] Mix = 0, /// Represents a VPN packet that should not be delayed and ideally cached pre-computed keys @@ -31,17 +36,11 @@ impl PacketMode { impl TryFrom for PacketMode { type Error = InvalidPacketMode; - fn try_from(value: u8) -> std::result::Result { + fn try_from(value: u8) -> Result { match value { _ if value == (PacketMode::Mix as u8) => Ok(Self::Mix), _ if value == (PacketMode::Vpn as u8) => Ok(Self::Vpn), - _ => Err(InvalidPacketMode), + v => Err(InvalidPacketMode { received: v }), } } } - -impl Default for PacketMode { - fn default() -> Self { - PacketMode::Mix - } -} diff --git a/common/nymsphinx/params/src/packet_sizes.rs b/common/nymsphinx/params/src/packet_sizes.rs index 3b2bbf2754..20275f5139 100644 --- a/common/nymsphinx/params/src/packet_sizes.rs +++ b/common/nymsphinx/params/src/packet_sizes.rs @@ -2,10 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::FRAG_ID_LEN; -use nymsphinx_types::header::HEADER_SIZE; -use nymsphinx_types::PAYLOAD_OVERHEAD_SIZE; +use nym_sphinx_types::header::HEADER_SIZE; +use nym_sphinx_types::PAYLOAD_OVERHEAD_SIZE; use std::convert::TryFrom; use std::str::FromStr; +use thiserror::Error; // it's up to the smart people to figure those values out : ) const REGULAR_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 2 * 1024; @@ -19,7 +20,6 @@ const ACK_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + ACK_IV_SIZE const EXTENDED_PACKET_SIZE_8: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 8 * 1024; const EXTENDED_PACKET_SIZE_16: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 16 * 1024; const EXTENDED_PACKET_SIZE_32: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 32 * 1024; - const EXTENDED_PACKET_SIZE_10: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 10 * 1024; const EXTENDED_PACKET_SIZE_15: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 15 * 1024; const EXTENDED_PACKET_SIZE_20: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 20 * 1024; @@ -31,16 +31,23 @@ const EXTENDED_PACKET_SIZE_200: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 20 const EXTENDED_PACKET_SIZE_250: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 250 * 1024; const EXTENDED_PACKET_SIZE_500: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 500 * 1024; -#[derive(Debug)] -pub struct InvalidPacketSize; +#[derive(Debug, Error)] +pub enum InvalidPacketSize { + #[error("{received} is not a valid packet size tag")] + UnknownPacketTag { received: u8 }, -#[derive(Debug)] -pub struct InvalidExtendedPacketSize; + #[error("{received} is not a valid extended packet size variant")] + UnknownExtendedPacketVariant { received: String }, + + #[error("{received} does not correspond with any known packet size")] + UnknownPacketSize { received: usize }, +} #[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum PacketSize { // for example instant messaging use case + #[default] RegularPacket = 1, // for sending SURB-ACKs @@ -88,7 +95,9 @@ impl FromStr for PacketSize { "extended200" => Ok(Self::ExtendedPacket200), "extended250" => Ok(Self::ExtendedPacket250), "extended500" => Ok(Self::ExtendedPacket500), - _ => Err(InvalidPacketSize), + s => Err(InvalidPacketSize::UnknownExtendedPacketVariant { + received: s.to_string(), + }), } } } @@ -96,7 +105,7 @@ impl FromStr for PacketSize { impl TryFrom for PacketSize { type Error = InvalidPacketSize; - fn try_from(value: u8) -> std::result::Result { + fn try_from(value: u8) -> Result { match value { _ if value == (PacketSize::RegularPacket as u8) => Ok(Self::RegularPacket), _ if value == (PacketSize::AckPacket as u8) => Ok(Self::AckPacket), @@ -113,7 +122,7 @@ impl TryFrom for PacketSize { _ if value == (PacketSize::ExtendedPacket200 as u8) => Ok(Self::ExtendedPacket200), _ if value == (PacketSize::ExtendedPacket250 as u8) => Ok(Self::ExtendedPacket250), _ if value == (PacketSize::ExtendedPacket500 as u8) => Ok(Self::ExtendedPacket500), - _ => Err(InvalidPacketSize), + v => Err(InvalidPacketSize::UnknownPacketTag { received: v }), } } } @@ -147,7 +156,7 @@ impl PacketSize { self.size() - HEADER_SIZE } - pub fn get_type(size: usize) -> std::result::Result { + pub fn get_type(size: usize) -> Result { if PacketSize::RegularPacket.size() == size { Ok(PacketSize::RegularPacket) } else if PacketSize::AckPacket.size() == size { @@ -179,7 +188,7 @@ impl PacketSize { } else if PacketSize::ExtendedPacket500.size() == size { Ok(PacketSize::ExtendedPacket500) } else { - Err(InvalidPacketSize) + Err(InvalidPacketSize::UnknownPacketSize { received: size }) } } @@ -211,9 +220,16 @@ impl PacketSize { } } -impl Default for PacketSize { - fn default() -> Self { - PacketSize::RegularPacket +#[cfg(test)] +mod tests { + use super::*; + use crate::AckEncryptionAlgorithm; + use nym_crypto::symmetric::stream_cipher::IvSizeUser; + + #[test] + fn ack_iv_size_assertion() { + let iv_size = AckEncryptionAlgorithm::iv_size(); + assert_eq!(iv_size, ACK_IV_SIZE); } } diff --git a/common/nymsphinx/src/lib.rs b/common/nymsphinx/src/lib.rs index bbc69c2682..a9e2d376e4 100644 --- a/common/nymsphinx/src/lib.rs +++ b/common/nymsphinx/src/lib.rs @@ -7,16 +7,16 @@ pub mod receiver; pub mod utils; // re-export sub-crates -pub use nymsphinx_acknowledgements as acknowledgements; -pub use nymsphinx_addressing as addressing; -pub use nymsphinx_anonymous_replies as anonymous_replies; -pub use nymsphinx_chunking as chunking; -pub use nymsphinx_cover as cover; -pub use nymsphinx_forwarding as forwarding; +pub use nym_sphinx_acknowledgements as acknowledgements; +pub use nym_sphinx_addressing as addressing; +pub use nym_sphinx_anonymous_replies as anonymous_replies; +pub use nym_sphinx_chunking as chunking; +pub use nym_sphinx_cover as cover; +pub use nym_sphinx_forwarding as forwarding; #[cfg(not(target_arch = "wasm32"))] -pub use nymsphinx_framing as framing; -pub use nymsphinx_params as params; -pub use nymsphinx_types::*; +pub use nym_sphinx_framing as framing; +pub use nym_sphinx_params as params; +pub use nym_sphinx_types::*; // TEMP UNTIL FURTHER REFACTORING pub use preparer::payload::NymsphinxPayloadBuilder; diff --git a/common/nymsphinx/src/message.rs b/common/nymsphinx/src/message.rs index f9b32daa35..7182e5e254 100644 --- a/common/nymsphinx/src/message.rs +++ b/common/nymsphinx/src/message.rs @@ -2,16 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use crate::chunking; -use crypto::asymmetric::encryption; -use crypto::Digest; -use nymsphinx_addressing::clients::Recipient; -use nymsphinx_addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN; -use nymsphinx_anonymous_replies::requests::{ +use nym_crypto::asymmetric::encryption; +use nym_crypto::Digest; +use nym_sphinx_addressing::clients::Recipient; +use nym_sphinx_addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN; +use nym_sphinx_anonymous_replies::requests::{ InvalidReplyRequestError, RepliableMessage, RepliableMessageContent, ReplyMessage, ReplyMessageContent, }; -use nymsphinx_chunking::fragment::Fragment; -use nymsphinx_params::{PacketSize, ReplySurbKeyDigestAlgorithm}; +use nym_sphinx_chunking::fragment::Fragment; +use nym_sphinx_params::{PacketSize, ReplySurbKeyDigestAlgorithm}; use rand::Rng; use std::fmt::{Display, Formatter}; use thiserror::Error; diff --git a/common/nymsphinx/src/preparer/mod.rs b/common/nymsphinx/src/preparer/mod.rs index 19f68e30bc..d7445275ca 100644 --- a/common/nymsphinx/src/preparer/mod.rs +++ b/common/nymsphinx/src/preparer/mod.rs @@ -3,21 +3,21 @@ use crate::message::NymMessage; use crate::NymsphinxPayloadBuilder; -use nymsphinx_acknowledgements::surb_ack::SurbAck; -use nymsphinx_acknowledgements::AckKey; -use nymsphinx_addressing::clients::Recipient; -use nymsphinx_addressing::nodes::NymNodeRoutingAddress; -use nymsphinx_anonymous_replies::reply_surb::ReplySurb; -use nymsphinx_chunking::fragment::{Fragment, FragmentIdentifier}; -use nymsphinx_forwarding::packet::MixPacket; -use nymsphinx_params::packet_sizes::PacketSize; -use nymsphinx_params::DEFAULT_NUM_MIX_HOPS; -use nymsphinx_types::builder::SphinxPacketBuilder; -use nymsphinx_types::{delays, Delay}; +use nym_sphinx_acknowledgements::surb_ack::SurbAck; +use nym_sphinx_acknowledgements::AckKey; +use nym_sphinx_addressing::clients::Recipient; +use nym_sphinx_addressing::nodes::NymNodeRoutingAddress; +use nym_sphinx_anonymous_replies::reply_surb::ReplySurb; +use nym_sphinx_chunking::fragment::{Fragment, FragmentIdentifier}; +use nym_sphinx_forwarding::packet::MixPacket; +use nym_sphinx_params::packet_sizes::PacketSize; +use nym_sphinx_params::DEFAULT_NUM_MIX_HOPS; +use nym_sphinx_types::builder::SphinxPacketBuilder; +use nym_sphinx_types::{delays, Delay}; +use nym_topology::{NymTopology, NymTopologyError}; use rand::{CryptoRng, Rng}; use std::convert::TryFrom; use std::time::Duration; -use topology::{NymTopology, NymTopologyError}; pub(crate) mod payload; diff --git a/common/nymsphinx/src/preparer/payload.rs b/common/nymsphinx/src/preparer/payload.rs index 94acde03c8..60786d3f11 100644 --- a/common/nymsphinx/src/preparer/payload.rs +++ b/common/nymsphinx/src/preparer/payload.rs @@ -1,15 +1,15 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crypto::aes::cipher::{KeyIvInit, StreamCipher}; -use crypto::asymmetric::encryption; -use crypto::shared_key::new_ephemeral_shared_key; -use crypto::symmetric::stream_cipher; -use crypto::symmetric::stream_cipher::CipherKey; -use nymsphinx_acknowledgements::surb_ack::SurbAck; -use nymsphinx_anonymous_replies::SurbEncryptionKey; -use nymsphinx_chunking::fragment::Fragment; -use nymsphinx_params::{ +use nym_crypto::aes::cipher::{KeyIvInit, StreamCipher}; +use nym_crypto::asymmetric::encryption; +use nym_crypto::shared_key::new_ephemeral_shared_key; +use nym_crypto::symmetric::stream_cipher; +use nym_crypto::symmetric::stream_cipher::CipherKey; +use nym_sphinx_acknowledgements::surb_ack::SurbAck; +use nym_sphinx_anonymous_replies::SurbEncryptionKey; +use nym_sphinx_chunking::fragment::Fragment; +use nym_sphinx_params::{ PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySurbEncryptionAlgorithm, }; use rand::{CryptoRng, RngCore}; diff --git a/common/nymsphinx/src/receiver.rs b/common/nymsphinx/src/receiver.rs index 453f632f76..147a14788c 100644 --- a/common/nymsphinx/src/receiver.rs +++ b/common/nymsphinx/src/receiver.rs @@ -2,17 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 use crate::message::{NymMessage, NymMessageError, PaddedMessage, PlainMessage}; -use crypto::aes::cipher::{KeyIvInit, StreamCipher}; -use crypto::asymmetric::encryption; -use crypto::shared_key::recompute_shared_key; -use crypto::symmetric::stream_cipher; -use crypto::symmetric::stream_cipher::CipherKey; -use nymsphinx_anonymous_replies::requests::AnonymousSenderTag; -use nymsphinx_anonymous_replies::SurbEncryptionKey; -use nymsphinx_chunking::fragment::Fragment; -use nymsphinx_chunking::reconstruction::MessageReconstructor; -use nymsphinx_chunking::ChunkingError; -use nymsphinx_params::{ +use nym_crypto::aes::cipher::{KeyIvInit, StreamCipher}; +use nym_crypto::asymmetric::encryption; +use nym_crypto::shared_key::recompute_shared_key; +use nym_crypto::symmetric::stream_cipher; +use nym_crypto::symmetric::stream_cipher::CipherKey; +use nym_sphinx_anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx_anonymous_replies::SurbEncryptionKey; +use nym_sphinx_chunking::fragment::Fragment; +use nym_sphinx_chunking::reconstruction::MessageReconstructor; +use nym_sphinx_chunking::ChunkingError; +use nym_sphinx_params::{ PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySurbEncryptionAlgorithm, DEFAULT_NUM_MIX_HOPS, }; @@ -59,6 +59,9 @@ impl From for ReconstructedMessage { #[derive(Debug, Error)] pub enum MessageRecoveryError { + #[error("The received message did not contain enough bytes to recover the ephemeral public key. Got {provided}. required: {required}")] + NotEnoughBytesForEphemeralKey { provided: usize, required: usize }, + #[error("Recovered remote x25519 public key is invalid - {0}")] InvalidRemoteEphemeralKey(#[from] encryption::KeyRecoveryError), @@ -123,6 +126,13 @@ impl MessageReceiver { local_key: &encryption::PrivateKey, raw_enc_frag: &'a mut [u8], ) -> Result<&'a mut [u8], MessageRecoveryError> { + if raw_enc_frag.len() < encryption::PUBLIC_KEY_SIZE { + return Err(MessageRecoveryError::NotEnoughBytesForEphemeralKey { + provided: raw_enc_frag.len(), + required: encryption::PUBLIC_KEY_SIZE, + }); + } + // 1. recover remote encryption key let remote_key_bytes = &raw_enc_frag[..encryption::PUBLIC_KEY_SIZE]; let remote_ephemeral_key = encryption::PublicKey::from_bytes(remote_key_bytes)?; @@ -184,10 +194,10 @@ impl Default for MessageReceiver { #[cfg(test)] mod message_receiver { use super::*; - use crypto::asymmetric::identity; - use mixnet_contract_common::Layer; + use nym_crypto::asymmetric::identity; + use nym_mixnet_contract_common::Layer; + use nym_topology::{gateway, mix, NymTopology}; use std::collections::HashMap; - use topology::{gateway, mix, NymTopology}; // TODO: is it somehow maybe possible to move it to `topology` and have if conditionally // available to other modules? diff --git a/common/nymsphinx/types/Cargo.toml b/common/nymsphinx/types/Cargo.toml index 353de2b1b2..6be749396a 100644 --- a/common/nymsphinx/types/Cargo.toml +++ b/common/nymsphinx/types/Cargo.toml @@ -1,11 +1,14 @@ [package] -name = "nymsphinx-types" -version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +name = "nym-sphinx-types" +version = "0.2.0" +description = "Re-export sphinx packet types" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] -sphinx = { git = "https://github.com/nymtech/sphinx", rev="e05a1992522ed0afd3c6fcac160313ffc9bb306a" } -#sphinx = { path = "../../../../sphinx"} +sphinx-packet = { version = "0.1.0" } + +#[patch.crates-io] +#sphinx-packet = { path = "../../../../sphinx" } diff --git a/common/nymsphinx/types/src/lib.rs b/common/nymsphinx/types/src/lib.rs index d3f8d629e4..e80268ada0 100644 --- a/common/nymsphinx/types/src/lib.rs +++ b/common/nymsphinx/types/src/lib.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // re-exporting types and constants available in sphinx -pub use sphinx::{ +pub use sphinx_packet::{ constants::{ self, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, MAX_PATH_LENGTH, NODE_ADDRESS_LENGTH, PAYLOAD_KEY_SIZE, diff --git a/common/pemstore/Cargo.toml b/common/pemstore/Cargo.toml index db4b331327..1f1b8af550 100644 --- a/common/pemstore/Cargo.toml +++ b/common/pemstore/Cargo.toml @@ -1,10 +1,11 @@ [package] -name = "pemstore" -version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +name = "nym-pemstore" +description = "Store private-public keypairs in PEM format" +version = "0.2.0" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } [dependencies] pem = "0.8" diff --git a/common/published-crates.txt b/common/published-crates.txt new file mode 100644 index 0000000000..5aee0f41d8 --- /dev/null +++ b/common/published-crates.txt @@ -0,0 +1,8 @@ +# List of published common crates +nym-bin-common +nym-contracts-common +nym-crypto +nym-mixnet-contract-common +nym-pemstore +nym-sphinx-types +nym-vesting-contract-common diff --git a/common/socks5/ordered-buffer/Cargo.toml b/common/socks5/ordered-buffer/Cargo.toml index f5ea5545d8..faa16769db 100644 --- a/common/socks5/ordered-buffer/Cargo.toml +++ b/common/socks5/ordered-buffer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ordered-buffer" +name = "nym-ordered-buffer" version = "0.1.0" authors = ["Dave Hrycyszyn "] edition = "2021" @@ -7,4 +7,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -log = "0.4" +log = { workspace = true } +thiserror = "1.0.37" diff --git a/common/socks5/ordered-buffer/src/message.rs b/common/socks5/ordered-buffer/src/message.rs index f5f28f53b1..15627d7fe0 100644 --- a/common/socks5/ordered-buffer/src/message.rs +++ b/common/socks5/ordered-buffer/src/message.rs @@ -1,9 +1,16 @@ -use std::cmp::Ordering; +// Copyright 2020-2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 -#[derive(Debug, PartialEq, Eq)] +use std::cmp::Ordering; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, Eq)] pub enum MessageError { + #[error("the received message was empty")] NoData, - IndexTooShort, + + #[error("could not extract message index. Received {received} bytes, but expected {expected}")] + IndexTooShort { received: usize, expected: usize }, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -32,7 +39,10 @@ impl OrderedMessage { } if data.len() < 8 { - return Err(MessageError::IndexTooShort); + return Err(MessageError::IndexTooShort { + received: data.len(), + expected: 8, + }); } let index = u64::from_be_bytes([ data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], @@ -81,7 +91,13 @@ mod ordered_message_from_bytes { #[test] fn fails_when_data_is_too_short() { let result = OrderedMessage::try_from_bytes(vec![1, 2, 3]); - assert_eq!(Err(MessageError::IndexTooShort), result); + assert_eq!( + Err(MessageError::IndexTooShort { + received: 3, + expected: 8 + }), + result + ); } #[test] diff --git a/common/socks5/proxy-helpers/Cargo.toml b/common/socks5/proxy-helpers/Cargo.toml index 2ecd2a0507..88157a22c4 100644 --- a/common/socks5/proxy-helpers/Cargo.toml +++ b/common/socks5/proxy-helpers/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "proxy-helpers" +name = "nym-socks5-proxy-helpers" version = "0.1.0" authors = ["Jedrzej Stuczynski "] edition = "2021" @@ -8,18 +8,17 @@ edition = "2021" [dependencies] bytes = "1.0" -tokio = { version = "1.21.2", features = [ "net", "io-util", "sync", "macros", "time", "rt-multi-thread" ] } -tokio-util = { version = "0.7.3", 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 = { 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 # 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 = "0.3" -log = "0.4" +log = { workspace = true } # internal -client-connections = { path = "../../client-connections" } -ordered-buffer = { path = "../ordered-buffer" } -socks5-requests = { path = "../requests" } -task = { path = "../../task" } +nym-ordered-buffer = { path = "../ordered-buffer" } +nym-socks5-requests = { path = "../requests" } +nym-task = { path = "../../task" } [dev-dependencies] tokio-test = "0.4.2" diff --git a/common/socks5/proxy-helpers/src/connection_controller.rs b/common/socks5/proxy-helpers/src/connection_controller.rs index 97c4487af6..a466316bbb 100644 --- a/common/socks5/proxy-helpers/src/connection_controller.rs +++ b/common/socks5/proxy-helpers/src/connection_controller.rs @@ -1,18 +1,14 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use client_connections::{ConnectionCommand, ConnectionCommandSender}; use futures::channel::mpsc; use futures::StreamExt; use log::*; -use ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData}; -use socks5_requests::ConnectionId; -use std::{ - collections::{HashMap, HashSet}, - time::Duration, -}; -use task::ShutdownListener; -use tokio::time; +use nym_ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData}; +use nym_socks5_requests::{ConnectionId, NetworkData, SendRequest}; +use nym_task::connections::{ConnectionCommand, ConnectionCommandSender}; +use nym_task::TaskClient; +use std::collections::{HashMap, HashSet}; /// A generic message produced after reading from a socket/connection. It includes data that was /// actually read alongside boolean indicating whether the connection got closed so that @@ -36,9 +32,38 @@ pub type ControllerSender = mpsc::UnboundedSender; pub type ControllerReceiver = mpsc::UnboundedReceiver; pub enum ControllerCommand { - Insert(ConnectionId, ConnectionSender), - Remove(ConnectionId), - Send(ConnectionId, Vec, bool), + Insert { + connection_id: ConnectionId, + connection_sender: ConnectionSender, + }, + Remove { + connection_id: ConnectionId, + }, + Send { + connection_id: ConnectionId, + data: Vec, + is_closed: bool, + }, +} + +impl From for ControllerCommand { + fn from(value: NetworkData) -> Self { + ControllerCommand::Send { + connection_id: value.connection_id, + data: value.data, + is_closed: value.is_closed, + } + } +} + +impl From for ControllerCommand { + fn from(value: SendRequest) -> Self { + ControllerCommand::Send { + connection_id: value.conn_id, + data: value.data, + is_closed: value.local_closed, + } + } } struct ActiveConnection { @@ -53,7 +78,7 @@ impl ActiveConnection { let ordered_message = match OrderedMessage::try_from_bytes(payload) { Ok(msg) => msg, Err(err) => { - error!("Malformed ordered message - {:?}", err); + error!("Malformed ordered message - {err}"); return; } }; @@ -87,10 +112,6 @@ pub struct Controller { // Broadcast closed connections client_connection_tx: ConnectionCommandSender, - // The controller can broadcast active connections. This is useful in the network-requester - // where its used to query the client for lane queue lengths - broadcast_connections: BroadcastActiveConnections, - // TODO: this can potentially be abused to ddos and kill provider. Not sure at this point // how to handle it more gracefully @@ -98,14 +119,13 @@ pub struct Controller { // un-order messages. Note we don't ever expect to have more than 1-2 messages per connection here pending_messages: HashMap, bool)>>, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl Controller { pub fn new( client_connection_tx: ConnectionCommandSender, - broadcast_connections: BroadcastActiveConnections, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> (Self, ControllerSender) { let (sender, receiver) = mpsc::unbounded(); ( @@ -114,7 +134,6 @@ impl Controller { receiver, recently_closed: HashSet::new(), client_connection_tx, - broadcast_connections, pending_messages: HashMap::new(), shutdown, }, @@ -158,22 +177,13 @@ impl Controller { .unbounded_send(ConnectionCommand::Close(conn_id)) { if self.shutdown.is_shutdown_poll() { - log::debug!("Failed to send: {}", err); + log::debug!("Failed to send: {err}"); } else { - log::error!("Failed to send: {}", err); + log::error!("Failed to send: {err}"); } } } - fn broadcast_active_connections(&mut self) { - // What about the recently closed ones? Hopefully we can ignore them ... - let conn_ids = self.active_connections.keys().copied().collect(); - - self.client_connection_tx - .unbounded_send(ConnectionCommand::ActiveConnections(conn_ids)) - .unwrap(); - } - fn send_to_connection(&mut self, conn_id: ConnectionId, payload: Vec, is_closed: bool) { if let Some(active_connection) = self.active_connections.get_mut(&conn_id) { if !payload.is_empty() { @@ -197,7 +207,7 @@ impl Controller { socket_closed: active_connection.is_closed, }) { - error!("WTF IS THIS: {:?}", err); + error!("WTF IS THIS: {err}"); } // TODO: ABOVE UNWRAP CAUSED A CRASH IN A NORMAL USE!!!! @@ -230,35 +240,24 @@ impl Controller { } pub async fn run(&mut self) { - let mut interval = time::interval(Duration::from_millis(500)); - loop { tokio::select! { command = self.receiver.next() => match command { - Some(ControllerCommand::Send(conn_id, data, is_closed)) => { - self.send_to_connection(conn_id, data, is_closed) + Some(ControllerCommand::Send{connection_id, data, is_closed}) => { + self.send_to_connection(connection_id, data, is_closed) } - Some(ControllerCommand::Insert(conn_id, sender)) => { - self.insert_connection(conn_id, sender) + Some(ControllerCommand::Insert{connection_id, connection_sender}) => { + self.insert_connection(connection_id, connection_sender) } - Some(ControllerCommand::Remove(conn_id)) => self.remove_connection(conn_id), + Some(ControllerCommand::Remove{ connection_id }) => self.remove_connection(connection_id), None => { log::trace!("SOCKS5 Controller: Stopping since channel closed"); break; } }, - _ = interval.tick() => { - if self.broadcast_connections == BroadcastActiveConnections::On { - self.broadcast_active_connections(); - } - }, } } - #[cfg(not(target_arch = "wasm32"))] - tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv()) - .await - .expect("Task stopped without shutdown called"); - assert!(self.shutdown.is_shutdown_poll()); + self.shutdown.recv_timeout().await; log::debug!("SOCKS5 Controller: Exiting"); } } diff --git a/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs b/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs index 37403c33ad..548d1a0971 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs @@ -5,17 +5,17 @@ use super::MixProxySender; use super::SHUTDOWN_TIMEOUT; use crate::available_reader::AvailableReader; use bytes::Bytes; -use client_connections::LaneQueueLengths; -use client_connections::TransmissionLane; use futures::FutureExt; use futures::StreamExt; use log::*; -use ordered_buffer::OrderedMessageSender; -use socks5_requests::ConnectionId; +use nym_ordered_buffer::OrderedMessageSender; +use nym_socks5_requests::ConnectionId; +use nym_task::connections::LaneQueueLengths; +use nym_task::connections::TransmissionLane; +use nym_task::TaskClient; use std::fmt::Debug; use std::time::Duration; use std::{io, sync::Arc}; -use task::ShutdownListener; use tokio::select; use tokio::{net::tcp::OwnedReadHalf, sync::Notify, time::sleep}; @@ -54,7 +54,7 @@ where Some(data) => match data { Ok(data) => (data, false), Err(err) => { - error!(target: &*format!("({}) socks5 inbound", connection_id), "failed to read request from the socket - {}", err); + error!(target: &*format!("({connection_id}) socks5 inbound"), "failed to read request from the socket - {err}"); (Default::default(), true) } }, @@ -62,7 +62,7 @@ where }; debug!( - target: &*format!("({}) socks5 inbound", connection_id), + target: &*format!("({connection_id}) socks5 inbound"), "[{} bytes]\t{} → local → mixnet → remote → {}. Local closed: {}", read_data.len(), local_destination_address, @@ -103,7 +103,7 @@ where // Technically we already informed it when we sent the message to mixnet above debug!( - target: &*format!("({}) socks5 inbound", connection_id), + target: &*format!("({connection_id}) socks5 inbound"), "The local socket is closed - won't receive any more data. Informing remote about that..." ); } @@ -170,7 +170,7 @@ pub(super) async fn run_inbound( adapter_fn: F, shutdown_notify: Arc, lane_queue_lengths: Option, - mut shutdown_listener: ShutdownListener, + mut shutdown_listener: TaskClient, ) -> OwnedReadHalf where F: Fn(ConnectionId, Vec, bool) -> S + Send + 'static, diff --git a/common/socks5/proxy-helpers/src/proxy_runner/mod.rs b/common/socks5/proxy-helpers/src/proxy_runner/mod.rs index defcbf4f5f..c9d925ad4f 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner/mod.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner/mod.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::connection_controller::ConnectionReceiver; -use client_connections::LaneQueueLengths; -use socks5_requests::ConnectionId; +use nym_socks5_requests::ConnectionId; +use nym_task::connections::LaneQueueLengths; +use nym_task::TaskClient; use std::fmt::Debug; use std::{sync::Arc, time::Duration}; -use task::ShutdownListener; use tokio::{net::TcpStream, sync::Notify}; mod inbound; @@ -50,7 +50,7 @@ pub struct ProxyRunner { lane_queue_lengths: Option, // Listens to shutdown commands from higher up - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, } impl ProxyRunner @@ -66,7 +66,7 @@ where mix_sender: MixProxySender, connection_id: ConnectionId, lane_queue_lengths: Option, - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, ) -> Self { ProxyRunner { mix_receiver: Some(mix_receiver), diff --git a/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs b/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs index 78db73afb6..72d8824d2b 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs @@ -6,9 +6,9 @@ use crate::connection_controller::{ConnectionMessage, ConnectionReceiver}; use futures::FutureExt; use futures::StreamExt; use log::*; -use socks5_requests::ConnectionId; +use nym_socks5_requests::ConnectionId; +use nym_task::TaskClient; use std::{sync::Arc, time::Duration}; -use task::ShutdownListener; use tokio::io::AsyncWriteExt; use tokio::select; use tokio::{net::tcp::OwnedWriteHalf, sync::Notify, time::sleep, time::Instant}; @@ -23,7 +23,7 @@ async fn deal_with_message( connection_id: ConnectionId, ) -> bool { debug!( - target: &*format!("({}) socks5 outbound", connection_id), + target: &*format!("({connection_id}) socks5 outbound"), "[{} bytes]\t{} → remote → mixnet → local → {} Remote closed: {}", connection_message.payload.len(), remote_source_address, @@ -33,11 +33,11 @@ async fn deal_with_message( if let Err(err) = writer.write_all(&connection_message.payload).await { // the other half is probably going to blow up too (if not, this task also needs to notify the other one!!) - error!(target: &*format!("({}) socks5 outbound", connection_id), "failed to write response back to the socket - {}", err); + error!(target: &*format!("({connection_id}) socks5 outbound"), "failed to write response back to the socket - {err}"); return true; } if connection_message.socket_closed { - debug!(target: &*format!("({}) socks5 outbound", connection_id), + debug!(target: &*format!("({connection_id}) socks5 outbound"), "Remote socket got closed - closing the local socket too"); return true; } @@ -51,7 +51,7 @@ pub(super) async fn run_outbound( mut mix_receiver: ConnectionReceiver, connection_id: ConnectionId, shutdown_notify: Arc, - mut shutdown_listener: ShutdownListener, + mut shutdown_listener: TaskClient, ) -> (OwnedWriteHalf, ConnectionReceiver) { let shutdown_future = shutdown_notify.notified().then(|_| sleep(SHUTDOWN_TIMEOUT)); tokio::pin!(shutdown_future); diff --git a/common/socks5/requests/Cargo.toml b/common/socks5/requests/Cargo.toml index e7bd46d279..d100503c6a 100644 --- a/common/socks5/requests/Cargo.toml +++ b/common/socks5/requests/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "socks5-requests" +name = "nym-socks5-requests" version = "0.1.0" authors = ["Dave Hrycyszyn "] edition = "2021" @@ -7,5 +7,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nymsphinx-addressing = { path = "../../../common/nymsphinx/addressing" } -thiserror = "1" +thiserror = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } + +nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" } +nym-service-providers-common = { path = "../../../service-providers/common" } diff --git a/common/socks5/requests/src/lib.rs b/common/socks5/requests/src/lib.rs index b7da4498fa..ba81b231a4 100644 --- a/common/socks5/requests/src/lib.rs +++ b/common/socks5/requests/src/lib.rs @@ -1,12 +1,128 @@ -// Copyright 2020-2022 - Nym Technologies SA +// Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub mod msg; -pub mod network_requester_response; -pub mod request; -pub mod response; +use nym_service_providers_common::interface; +use nym_service_providers_common::interface::ServiceProviderMessagingError; +use thiserror::Error; -pub use msg::*; -pub use network_requester_response::*; pub use request::*; pub use response::*; +pub use version::*; + +pub mod request; +pub mod response; +pub mod version; + +pub type Socks5ProviderRequest = interface::Request; +pub type Socks5ProviderResponse = interface::Response; + +#[derive(Debug, Error)] +pub enum Socks5RequestError { + #[error("failed to deserialize received request: {source}")] + RequestDeserialization { + #[from] + source: RequestDeserializationError, + }, + + #[error("failed to deserialize received response: {source}")] + ResponseDeserialization { + #[from] + source: ResponseDeserializationError, + }, + + #[error(transparent)] + ProviderInterfaceError(#[from] ServiceProviderMessagingError), +} + +#[cfg(test)] +mod tests { + use super::*; + use nym_service_providers_common::interface::RequestContent; + + #[cfg(test)] + mod interface_backwards_compatibility { + use super::*; + use nym_service_providers_common::interface::ProviderInterfaceVersion; + + #[test] + fn old_client_vs_new_service_provider() { + let old_serialized_connect = vec![ + 0, 0, 2, 254, 34, 100, 192, 20, 13, 171, 0, 16, 56, 48, 46, 50, 52, 57, 46, 57, 57, + 46, 49, 52, 56, 58, 56, 48, 34, 112, 17, 182, 225, 6, 174, 216, 160, 41, 72, 236, + 160, 90, 156, 3, 250, 41, 243, 53, 191, 178, 218, 53, 170, 14, 185, 33, 94, 153, + 25, 41, 6, 82, 169, 187, 88, 246, 211, 57, 68, 225, 228, 231, 116, 29, 119, 235, + 160, 14, 156, 205, 66, 1, 75, 204, 204, 220, 14, 150, 191, 203, 174, 88, 121, 173, + 83, 219, 188, 164, 194, 212, 238, 228, 4, 128, 48, 105, 224, 83, 17, 246, 233, 16, + 235, 223, 68, 87, 13, 40, 34, 186, 218, 204, 126, 145, + ]; + + let new_deserialized = + Socks5ProviderRequest::try_from_bytes(&old_serialized_connect).unwrap(); + + match new_deserialized.content { + RequestContent::ProviderData(req) => match req.content { + Socks5RequestContent::Connect(connect_req) => { + assert_eq!(connect_req.remote_addr, "80.249.99.148:80".to_string()); + assert_eq!(connect_req.conn_id, 215647648274976171); + assert_eq!(connect_req.return_address, Some("3KRydEpanwjFhq5GAraVjRUF1Tno7w7oc4EwJYTGNo5J.RgZ7uMJHruBQqD5hC9Ghi3sqiTn6NycfM5qCfJz6yoM@9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J".parse().unwrap())); + } + _ => panic!("unexpected request"), + }, + _ => panic!("unexpected request"), + } + + let old_serialized_send = vec![ + 0, 1, 108, 102, 28, 19, 50, 178, 37, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 69, 84, + 32, 47, 49, 77, 66, 46, 122, 105, 112, 32, 72, 84, 84, 80, 47, 49, 46, 49, 13, 10, + 72, 111, 115, 116, 58, 32, 105, 112, 118, 52, 46, 100, 111, 119, 110, 108, 111, 97, + 100, 46, 116, 104, 105, 110, 107, 98, 114, 111, 97, 100, 98, 97, 110, 100, 46, 99, + 111, 109, 13, 10, 85, 115, 101, 114, 45, 65, 103, 101, 110, 116, 58, 32, 99, 117, + 114, 108, 47, 55, 46, 54, 56, 46, 48, 13, 10, 65, 99, 99, 101, 112, 116, 58, 32, + 42, 47, 42, 13, 10, 13, 10, + ]; + + let new_deserialized = + Socks5ProviderRequest::try_from_bytes(&old_serialized_send).unwrap(); + + match new_deserialized.content { + RequestContent::ProviderData(req) => match req.content { + Socks5RequestContent::Send(send_req) => { + assert_eq!(send_req.conn_id, 7810961472501196273); + assert_eq!(send_req.data.len(), 111); + assert!(!send_req.local_closed); + } + _ => panic!("unexpected request"), + }, + _ => panic!("unexpected request"), + } + } + + #[test] + fn new_client_vs_old_service_provider() { + let return_address = "3KRydEpanwjFhq5GAraVjRUF1Tno7w7oc4EwJYTGNo5J.RgZ7uMJHruBQqD5hC9Ghi3sqiTn6NycfM5qCfJz6yoM@9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J".parse().unwrap(); + + let new_connect = Socks5ProviderRequest::new_provider_data( + ProviderInterfaceVersion::Legacy, + Socks5Request::new_connect( + Socks5ProtocolVersion::Legacy, + 215647648274976171, + "80.249.99.148:80".to_string(), + Some(return_address), + ), + ); + + let legacy_serialised = new_connect.into_bytes(); + let old_serialized_connect = vec![ + 0, 0, 2, 254, 34, 100, 192, 20, 13, 171, 0, 16, 56, 48, 46, 50, 52, 57, 46, 57, 57, + 46, 49, 52, 56, 58, 56, 48, 34, 112, 17, 182, 225, 6, 174, 216, 160, 41, 72, 236, + 160, 90, 156, 3, 250, 41, 243, 53, 191, 178, 218, 53, 170, 14, 185, 33, 94, 153, + 25, 41, 6, 82, 169, 187, 88, 246, 211, 57, 68, 225, 228, 231, 116, 29, 119, 235, + 160, 14, 156, 205, 66, 1, 75, 204, 204, 220, 14, 150, 191, 203, 174, 88, 121, 173, + 83, 219, 188, 164, 194, 212, 238, 228, 4, 128, 48, 105, 224, 83, 17, 246, 233, 16, + 235, 223, 68, 87, 13, 40, 34, 186, 218, 204, 126, 145, + ]; + + assert_eq!(legacy_serialised, old_serialized_connect); + } + } +} diff --git a/common/socks5/requests/src/msg.rs b/common/socks5/requests/src/msg.rs deleted file mode 100644 index 2b688d8214..0000000000 --- a/common/socks5/requests/src/msg.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2020-2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use thiserror::Error; - -use crate::network_requester_response::{Error as NrError, NetworkRequesterResponse}; -use crate::request::{Request, RequestError}; -use crate::response::{Response, ResponseError}; - -#[derive(Debug, Error)] -pub enum MessageError { - #[error("{0}")] - Request(RequestError), - - #[error("{0:?}")] - Response(ResponseError), - - #[error("{0}")] - NetworkRequesterResponseError(NrError), - - #[error("no data")] - NoData, - - #[error("unknown message type received")] - UnknownMessageType, -} - -#[derive(Debug)] -pub enum Message { - Request(Request), - Response(Response), - NetworkRequesterResponse(NetworkRequesterResponse), -} - -impl Message { - const REQUEST_FLAG: u8 = 0; - const RESPONSE_FLAG: u8 = 1; - const NR_RESPONSE_FLAG: u8 = 2; - - pub fn conn_id(&self) -> u64 { - match self { - Message::Request(req) => match req { - Request::Connect(c) => c.conn_id, - Request::Send(conn_id, _, _) => *conn_id, - }, - Message::Response(resp) => resp.connection_id, - Message::NetworkRequesterResponse(resp) => resp.connection_id, - } - } - - pub fn size(&self) -> usize { - match self { - Message::Request(req) => match req { - Request::Connect(_) => 0, - Request::Send(_, data, _) => data.len(), - }, - Message::Response(resp) => resp.data.len(), - Message::NetworkRequesterResponse(_) => 0, - } - } - - pub fn try_from_bytes(b: &[u8]) -> Result { - if b.is_empty() { - return Err(MessageError::NoData); - } - - if b[0] == Self::REQUEST_FLAG { - Request::try_from_bytes(&b[1..]) - .map(Message::Request) - .map_err(MessageError::Request) - } else if b[0] == Self::RESPONSE_FLAG { - Response::try_from_bytes(&b[1..]) - .map(Message::Response) - .map_err(MessageError::Response) - } else if b[0] == Self::NR_RESPONSE_FLAG { - NetworkRequesterResponse::try_from_bytes(&b[1..]) - .map(Message::NetworkRequesterResponse) - .map_err(MessageError::NetworkRequesterResponseError) - } else { - Err(MessageError::UnknownMessageType) - } - } - - pub fn into_bytes(self) -> Vec { - match self { - Self::Request(r) => std::iter::once(Self::REQUEST_FLAG) - .chain(r.into_bytes().iter().cloned()) - .collect(), - Self::Response(r) => std::iter::once(Self::RESPONSE_FLAG) - .chain(r.into_bytes().iter().cloned()) - .collect(), - Self::NetworkRequesterResponse(r) => std::iter::once(Self::NR_RESPONSE_FLAG) - .chain(r.into_bytes().iter().cloned()) - .collect(), - } - } -} diff --git a/common/socks5/requests/src/network_requester_response.rs b/common/socks5/requests/src/network_requester_response.rs deleted file mode 100644 index 64e459dfb0..0000000000 --- a/common/socks5/requests/src/network_requester_response.rs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::ConnectionId; - -#[derive(Debug)] -pub struct NetworkRequesterResponse { - pub connection_id: ConnectionId, - pub network_requester_error: String, -} - -#[derive(Debug, thiserror::Error, PartialEq, Eq)] -pub enum Error { - #[error("no data provided")] - NoData, - - #[error("not enough bytes to recover the connection id")] - ConnectionIdTooShort, - - #[error("message is not utf8 encoded")] - MalformedErrorMessage(#[from] std::string::FromUtf8Error), -} - -impl NetworkRequesterResponse { - pub fn new(connection_id: ConnectionId, network_requester_error: String) -> Self { - NetworkRequesterResponse { - connection_id, - network_requester_error, - } - } - - pub fn try_from_bytes(b: &[u8]) -> Result { - if b.is_empty() { - return Err(Error::NoData); - } - - if b.len() < 8 { - return Err(Error::ConnectionIdTooShort); - } - - let mut connection_id_bytes = b.to_vec(); - let network_requester_error_bytes = connection_id_bytes.split_off(8); - - let connection_id = u64::from_be_bytes([ - connection_id_bytes[0], - connection_id_bytes[1], - connection_id_bytes[2], - connection_id_bytes[3], - connection_id_bytes[4], - connection_id_bytes[5], - connection_id_bytes[6], - connection_id_bytes[7], - ]); - let network_requester_error = String::from_utf8(network_requester_error_bytes)?; - - Ok(NetworkRequesterResponse { - connection_id, - network_requester_error, - }) - } - - pub fn into_bytes(self) -> Vec { - self.connection_id - .to_be_bytes() - .iter() - .cloned() - .chain(self.network_requester_error.into_bytes().into_iter()) - .collect() - } -} - -#[cfg(test)] -mod network_requester_response_serde_tests { - use super::*; - - #[test] - fn simple_serde() { - let conn_id = 42; - let network_requester_error = String::from("This is a test msg"); - let response = NetworkRequesterResponse::new(conn_id, network_requester_error.clone()); - let bytes = response.into_bytes(); - let deserialized_response = NetworkRequesterResponse::try_from_bytes(&bytes).unwrap(); - - assert_eq!(conn_id, deserialized_response.connection_id); - assert_eq!( - network_requester_error, - deserialized_response.network_requester_error - ); - } - - #[test] - fn deserialization_errors() { - let err = NetworkRequesterResponse::try_from_bytes(&[]).err().unwrap(); - assert_eq!(err, Error::NoData); - - let bytes: [u8; 5] = [1, 2, 3, 4, 5]; - let err = NetworkRequesterResponse::try_from_bytes(&bytes) - .err() - .unwrap(); - assert_eq!(err, Error::ConnectionIdTooShort); - - let bytes: Vec = 42u64 - .to_be_bytes() - .into_iter() - .chain([0, 159, 146, 150].into_iter()) - .collect(); - let err = NetworkRequesterResponse::try_from_bytes(&bytes) - .err() - .unwrap(); - assert!(matches!(err, Error::MalformedErrorMessage(_))); - } -} diff --git a/common/socks5/requests/src/request.rs b/common/socks5/requests/src/request.rs index 3148f6cd84..f296407c0b 100644 --- a/common/socks5/requests/src/request.rs +++ b/common/socks5/requests/src/request.rs @@ -1,7 +1,9 @@ -// Copyright 2020-2022 - Nym Technologies SA +// Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nymsphinx_addressing::clients::{Recipient, RecipientFormattingError}; +use crate::{Socks5ProtocolVersion, Socks5RequestError, Socks5Response}; +use nym_service_providers_common::interface::{Serializable, ServiceProviderRequest}; +use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError}; use std::convert::TryFrom; use thiserror::Error; @@ -16,19 +18,19 @@ pub enum RequestFlag { } impl TryFrom for RequestFlag { - type Error = RequestError; + type Error = RequestDeserializationError; - fn try_from(value: u8) -> Result { + fn try_from(value: u8) -> Result { match value { _ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect), _ if value == (RequestFlag::Send as u8) => Ok(Self::Send), - _ => Err(RequestError::UnknownRequestFlag), + value => Err(RequestDeserializationError::UnknownRequestFlag { value }), } } } #[derive(Debug, Error)] -pub enum RequestError { +pub enum RequestDeserializationError { #[error("not enough bytes to recover the length of the address")] AddressLengthTooShort, @@ -41,8 +43,8 @@ pub enum RequestError { #[error("no data provided")] NoData, - #[error("request of unknown type")] - UnknownRequestFlag, + #[error("{value} is not a valid request flag")] + UnknownRequestFlag { value: u8 }, #[error("too short return address")] ReturnAddressTooShort, @@ -51,13 +53,13 @@ pub enum RequestError { MalformedReturnAddress(RecipientFormattingError), } -impl RequestError { +impl RequestDeserializationError { pub fn is_malformed_return(&self) -> bool { - matches!(self, RequestError::MalformedReturnAddress(_)) + matches!(self, RequestDeserializationError::MalformedReturnAddress(_)) } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ConnectRequest { // TODO: is connection_id redundant now? pub conn_id: ConnectionId, @@ -65,27 +67,128 @@ pub struct ConnectRequest { pub return_address: Option, } +#[derive(Debug, Clone)] +pub struct SendRequest { + pub conn_id: ConnectionId, + pub data: Vec, + pub local_closed: bool, +} + +#[derive(Debug, Clone)] +pub struct Socks5Request { + pub protocol_version: Socks5ProtocolVersion, + pub content: Socks5RequestContent, +} + +impl Serializable for Socks5Request { + type Error = Socks5RequestError; + + // legacy requests had the format of + // 0 (Message::REQUEST_FLAG) || 0 (RequestFlag::Connect) || for connect requests + // 0 (Message::REQUEST_FLAG) || 1 (RequestFlag::Send) || for send requests + // the updated formats use + // 3 (Socks5ProtocolVersion) || 0 (RequestFlag::Connect) || for connect requests + // 3 (Socks5ProtocolVersion) || 1 (RequestFlag::Send) || for send requests + // in both cases, the actual data is serialized the same way, so the process is quite straight forward + fn into_bytes(self) -> Vec { + if let Some(version) = self.protocol_version.as_u8() { + std::iter::once(version) + .chain(self.content.into_bytes().into_iter()) + .collect() + } else { + std::iter::once(Self::LEGACY_TYPE_TAG) + .chain(self.content.into_bytes()) + .collect() + } + } + + fn try_from_bytes(b: &[u8]) -> Result { + if b.is_empty() { + return Err(RequestDeserializationError::NoData.into()); + } + + let protocol_version = Socks5ProtocolVersion::from(b[0]); + Ok(Socks5Request { + protocol_version, + content: Socks5RequestContent::try_from_bytes(&b[1..])?, + }) + } +} + +impl ServiceProviderRequest for Socks5Request { + type ProtocolVersion = Socks5ProtocolVersion; + type Response = Socks5Response; + type Error = Socks5RequestError; + + fn provider_specific_version(&self) -> Self::ProtocolVersion { + self.protocol_version + } + + fn max_supported_version() -> Self::ProtocolVersion { + Socks5ProtocolVersion::new_current() + } +} + +impl Socks5Request { + // type tag that used to be prepended to all request messages + const LEGACY_TYPE_TAG: u8 = 0x00; + + pub fn new( + protocol_version: Socks5ProtocolVersion, + content: Socks5RequestContent, + ) -> Socks5Request { + Socks5Request { + protocol_version, + content, + } + } + + pub fn new_connect( + protocol_version: Socks5ProtocolVersion, + conn_id: ConnectionId, + remote_addr: RemoteAddress, + return_address: Option, + ) -> Socks5Request { + Socks5Request { + protocol_version, + content: Socks5RequestContent::new_connect(conn_id, remote_addr, return_address), + } + } + + pub fn new_send( + protocol_version: Socks5ProtocolVersion, + conn_id: ConnectionId, + data: Vec, + local_closed: bool, + ) -> Socks5Request { + Socks5Request { + protocol_version, + content: Socks5RequestContent::new_send(conn_id, data, local_closed), + } + } +} + /// A request from a SOCKS5 client that a Nym Socks5 service provider should /// take an action for an application using a (probably local) Nym Socks5 proxy. -#[derive(Debug)] -pub enum Request { +#[derive(Debug, Clone)] +pub enum Socks5RequestContent { /// Start a new TCP connection to the specified `RemoteAddress` and send /// the request data up the connection. /// All responses produced on this `ConnectionId` should come back to the specified `Recipient` Connect(Box), /// Re-use an existing TCP connection, sending more request data up it. - Send(ConnectionId, Vec, bool), + Send(SendRequest), } -impl Request { +impl Socks5RequestContent { /// Construct a new Request::Connect instance pub fn new_connect( conn_id: ConnectionId, remote_addr: RemoteAddress, return_address: Option, - ) -> Request { - Request::Connect(Box::new(ConnectRequest { + ) -> Socks5RequestContent { + Socks5RequestContent::Connect(Box::new(ConnectRequest { conn_id, remote_addr, return_address, @@ -93,8 +196,16 @@ impl Request { } /// Construct a new Request::Send instance - pub fn new_send(conn_id: ConnectionId, data: Vec, local_closed: bool) -> Request { - Request::Send(conn_id, data, local_closed) + pub fn new_send( + conn_id: ConnectionId, + data: Vec, + local_closed: bool, + ) -> Socks5RequestContent { + Socks5RequestContent::Send(SendRequest { + conn_id, + data, + local_closed, + }) } /// Deserialize the request type, connection id, destination address and port, @@ -111,23 +222,23 @@ impl Request { /// The request_flag tells us whether this is a new connection request (`new_connect`), /// an already-established connection we should send up (`new_send`), or /// a request to close an established connection (`new_close`). - pub fn try_from_bytes(b: &[u8]) -> Result { + pub fn try_from_bytes(b: &[u8]) -> Result { // each request needs to at least contain flag and ConnectionId if b.is_empty() { - return Err(RequestError::NoData); + return Err(RequestDeserializationError::NoData); } if b.len() < 9 { - return Err(RequestError::ConnectionIdTooShort); + return Err(RequestDeserializationError::ConnectionIdTooShort); } - let connection_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]); + let conn_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]); match RequestFlag::try_from(b[0])? { RequestFlag::Connect => { let connect_request_bytes = &b[9..]; // we need to be able to read at least 2 bytes that specify address length if connect_request_bytes.len() < 2 { - return Err(RequestError::AddressLengthTooShort); + return Err(RequestDeserializationError::AddressLengthTooShort); } let address_length = @@ -135,7 +246,7 @@ impl Request { as usize; if connect_request_bytes.len() < 2 + address_length { - return Err(RequestError::AddressTooShort); + return Err(RequestDeserializationError::AddressTooShort); } let address_start = 2; @@ -150,19 +261,19 @@ impl Request { None } else { if recipient_data_bytes.len() != Recipient::LEN { - return Err(RequestError::ReturnAddressTooShort); + return Err(RequestDeserializationError::ReturnAddressTooShort); } let mut return_bytes = [0u8; Recipient::LEN]; return_bytes.copy_from_slice(&recipient_data_bytes[..Recipient::LEN]); Some( Recipient::try_from_bytes(return_bytes) - .map_err(RequestError::MalformedReturnAddress)?, + .map_err(RequestDeserializationError::MalformedReturnAddress)?, ) }; - Ok(Request::new_connect( - connection_id, + Ok(Socks5RequestContent::new_connect( + conn_id, remote_address, return_address, )) @@ -171,7 +282,11 @@ impl Request { let local_closed = b[9] != 0; let data = b[10..].to_vec(); - Ok(Request::Send(connection_id, data, local_closed)) + Ok(Socks5RequestContent::Send(SendRequest { + conn_id, + data, + local_closed, + })) } } } @@ -182,7 +297,7 @@ impl Request { pub fn into_bytes(self) -> Vec { match self { // connect is: CONN_FLAG || CONN_ID || REMOTE_LEN || REMOTE || RETURN - Request::Connect(req) => { + Socks5RequestContent::Connect(req) => { let remote_address_bytes = req.remote_addr.into_bytes(); let remote_address_bytes_len = remote_address_bytes.len() as u16; @@ -197,10 +312,10 @@ impl Request { iter.collect() } } - Request::Send(conn_id, data, local_closed) => std::iter::once(RequestFlag::Send as u8) - .chain(conn_id.to_be_bytes().into_iter()) - .chain(std::iter::once(local_closed as u8)) - .chain(data.into_iter()) + Socks5RequestContent::Send(req) => std::iter::once(RequestFlag::Send as u8) + .chain(req.conn_id.to_be_bytes().into_iter()) + .chain(std::iter::once(req.local_closed as u8)) + .chain(req.data.into_iter()) .collect(), } } @@ -216,8 +331,8 @@ mod request_deserialization_tests { #[test] fn returns_error_when_zero_bytes() { let request_bytes = Vec::new(); - match Request::try_from_bytes(&request_bytes).unwrap_err() { - RequestError::NoData => {} + match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() { + RequestDeserializationError::NoData => {} _ => unreachable!(), } } @@ -225,8 +340,8 @@ mod request_deserialization_tests { #[test] fn returns_error_when_connection_id_too_short() { let request_bytes = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7].to_vec(); // 7 bytes connection id - match Request::try_from_bytes(&request_bytes).unwrap_err() { - RequestError::ConnectionIdTooShort => {} + match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() { + RequestDeserializationError::ConnectionIdTooShort => {} _ => unreachable!(), } } @@ -241,13 +356,13 @@ mod request_deserialization_tests { let request_bytes1 = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8].to_vec(); // 8 bytes connection id, 0 bytes address length (2 were expected) let request_bytes2 = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec(); // 8 bytes connection id, 1 bytes address length (2 were expected) - match Request::try_from_bytes(&request_bytes1).unwrap_err() { - RequestError::AddressLengthTooShort => {} + match Socks5RequestContent::try_from_bytes(&request_bytes1).unwrap_err() { + RequestDeserializationError::AddressLengthTooShort => {} _ => unreachable!(), } - match Request::try_from_bytes(&request_bytes2).unwrap_err() { - RequestError::AddressLengthTooShort => {} + match Socks5RequestContent::try_from_bytes(&request_bytes2).unwrap_err() { + RequestDeserializationError::AddressLengthTooShort => {} _ => unreachable!(), } } @@ -255,8 +370,8 @@ mod request_deserialization_tests { #[test] fn returns_error_when_address_too_short_for_given_address_length() { let request_bytes = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1].to_vec(); // 8 bytes connection id, 2 bytes address length, missing address - match Request::try_from_bytes(&request_bytes).unwrap_err() { - RequestError::AddressTooShort => {} + match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() { + RequestDeserializationError::AddressTooShort => {} _ => unreachable!(), } } @@ -295,8 +410,8 @@ mod request_deserialization_tests { .chain(recipient_bytes.iter().take(40).cloned()) .collect(); - match Request::try_from_bytes(&request_bytes).unwrap_err() { - RequestError::ReturnAddressTooShort => {} + match Socks5RequestContent::try_from_bytes(&request_bytes).unwrap_err() { + RequestDeserializationError::ReturnAddressTooShort => {} _ => unreachable!(), } } @@ -338,7 +453,7 @@ mod request_deserialization_tests { .cloned() .chain(recipient_bytes.into_iter()) .collect(); - assert!(Request::try_from_bytes(&request_bytes) + assert!(Socks5RequestContent::try_from_bytes(&request_bytes) .unwrap_err() .is_malformed_return()); } @@ -376,9 +491,9 @@ mod request_deserialization_tests { .chain(recipient_bytes.into_iter()) .collect(); - let request = Request::try_from_bytes(&request_bytes).unwrap(); + let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap(); match request { - Request::Connect(req) => { + Socks5RequestContent::Connect(req) => { assert_eq!("foo.com".to_string(), req.remote_addr); assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id); assert_eq!( @@ -423,9 +538,9 @@ mod request_deserialization_tests { .chain(recipient_bytes.into_iter()) .collect(); - let request = Request::try_from_bytes(&request_bytes).unwrap(); + let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap(); match request { - Request::Connect(req) => { + Socks5RequestContent::Connect(req) => { assert_eq!("foo.com".to_string(), req.remote_addr); assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id); assert_eq!( @@ -446,9 +561,13 @@ mod request_deserialization_tests { fn works_when_request_is_sized_properly_even_without_data() { // correct 8 bytes of connection_id, 1 byte of local_closed and 0 bytes request data let request_bytes = [RequestFlag::Send as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec(); - let request = Request::try_from_bytes(&request_bytes).unwrap(); + let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap(); match request { - Request::Send(conn_id, data, local_closed) => { + Socks5RequestContent::Send(SendRequest { + conn_id, + data, + local_closed, + }) => { assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id); assert_eq!(Vec::::new(), data); assert!(!local_closed) @@ -477,9 +596,13 @@ mod request_deserialization_tests { ] .to_vec(); - let request = Request::try_from_bytes(&request_bytes).unwrap(); + let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap(); match request { - Request::Send(conn_id, data, local_closed) => { + Socks5RequestContent::Send(SendRequest { + conn_id, + data, + local_closed, + }) => { assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id); assert_eq!(vec![255, 255, 255], data); assert!(!local_closed) diff --git a/common/socks5/requests/src/response.rs b/common/socks5/requests/src/response.rs index 07d78be964..6e698a2b37 100644 --- a/common/socks5/requests/src/response.rs +++ b/common/socks5/requests/src/response.rs @@ -1,46 +1,239 @@ -// Copyright 2020-2022 - Nym Technologies SA +// Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::{ConnectionId, Socks5ProtocolVersion, Socks5RequestError}; +use nym_service_providers_common::interface::{Serializable, ServiceProviderResponse}; use thiserror::Error; -use crate::ConnectionId; +// don't start tags from 0 for easier backwards compatibility since `NetworkData` +// used to be a `Response` with tag 1 +// and `ConnectionError` used to be `NetworkRequesterResponse` with tag 2 +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum ResponseFlag { + NetworkData = 1, + ConnectionError = 2, +} + +impl TryFrom for ResponseFlag { + type Error = ResponseDeserializationError; + + fn try_from(value: u8) -> Result { + match value { + _ if value == (ResponseFlag::NetworkData as u8) => Ok(Self::NetworkData), + _ if value == (ResponseFlag::ConnectionError as u8) => Ok(Self::ConnectionError), + value => Err(ResponseDeserializationError::UnknownResponseFlag { value }), + } + } +} #[derive(Debug, Error, PartialEq, Eq)] -pub enum ResponseError { +pub enum ResponseDeserializationError { #[error("not enough bytes to recover the connection id")] ConnectionIdTooShort, + + #[error("{value} is not a valid response flag")] + UnknownResponseFlag { value: u8 }, + #[error("no data provided")] NoData, + + #[error("message is not utf8 encoded: {source}")] + MalformedErrorMessage { + #[from] + source: std::string::FromUtf8Error, + }, } -/// A remote network response retrieved by the Socks5 service provider. This + +#[derive(Debug)] +pub struct Socks5Response { + pub protocol_version: Socks5ProtocolVersion, + pub content: Socks5ResponseContent, +} + +impl Serializable for Socks5Response { + type Error = Socks5RequestError; + + // legacy responses had the format of + // 1 (Message::RESPONSE_FLAG) || for data responses + // 2 (Message::NR_RESPONSE_FLAG) || for error responses + // the updated formats use + // 3 (Socks5ProtocolVersion) || 0 (ResponseFlag::NetworkData) || for data responses + // 3 (Socks5ProtocolVersion) || 1 (ResponseFlag::ConnectionError) || for error responses + // so for serialization an optional version tag is prepended + // and in deserialization it's just the case of shifting the buffer in case of non-legacy response payload + fn into_bytes(self) -> Vec { + if let Some(version) = self.protocol_version.as_u8() { + std::iter::once(version) + .chain(self.content.into_bytes().into_iter()) + .collect() + } else { + self.content.into_bytes() + } + } + + fn try_from_bytes(b: &[u8]) -> Result { + if b.is_empty() { + return Err(ResponseDeserializationError::NoData.into()); + } + + let protocol_version = Socks5ProtocolVersion::from(b[0]); + let content = if protocol_version.is_legacy() { + Socks5ResponseContent::try_from_bytes(b) + } else { + Socks5ResponseContent::try_from_bytes(&b[1..]) + }?; + Ok(Socks5Response { + protocol_version, + content, + }) + } +} + +impl ServiceProviderResponse for Socks5Response {} + +impl Socks5Response { + pub fn new( + protocol_version: Socks5ProtocolVersion, + content: Socks5ResponseContent, + ) -> Socks5Response { + Socks5Response { + protocol_version, + content, + } + } + + pub fn new_network_data( + protocol_version: Socks5ProtocolVersion, + connection_id: ConnectionId, + data: Vec, + is_closed: bool, + ) -> Socks5Response { + Socks5Response { + protocol_version, + content: Socks5ResponseContent::new_network_data(connection_id, data, is_closed), + } + } + + pub fn new_closed_empty( + protocol_version: Socks5ProtocolVersion, + connection_id: ConnectionId, + ) -> Socks5Response { + Socks5Response { + protocol_version, + content: Socks5ResponseContent::new_closed_empty(connection_id), + } + } + + pub fn new_connection_error( + protocol_version: Socks5ProtocolVersion, + connection_id: ConnectionId, + error_message: String, + ) -> Socks5Response { + Socks5Response { + protocol_version, + content: Socks5ResponseContent::new_connection_error(connection_id, error_message), + } + } +} + +#[derive(Debug)] +pub enum Socks5ResponseContent { + NetworkData(NetworkData), + ConnectionError(ConnectionError), +} + +impl Socks5ResponseContent { + pub fn new_network_data( + connection_id: ConnectionId, + data: Vec, + is_closed: bool, + ) -> Socks5ResponseContent { + Socks5ResponseContent::NetworkData(NetworkData::new(connection_id, data, is_closed)) + } + + pub fn new_closed_empty(connection_id: ConnectionId) -> Socks5ResponseContent { + Socks5ResponseContent::NetworkData(NetworkData::new_closed_empty(connection_id)) + } + + pub fn new_connection_error( + connection_id: ConnectionId, + error_message: String, + ) -> Socks5ResponseContent { + Socks5ResponseContent::ConnectionError(ConnectionError::new(connection_id, error_message)) + } + + pub fn into_bytes(self) -> Vec { + match self { + Socks5ResponseContent::NetworkData(res) => { + std::iter::once(ResponseFlag::NetworkData as u8) + .chain(res.into_bytes().into_iter()) + .collect() + } + Socks5ResponseContent::ConnectionError(res) => { + std::iter::once(ResponseFlag::ConnectionError as u8) + .chain(res.into_bytes().into_iter()) + .collect() + } + } + } + + pub fn try_from_bytes(b: &[u8]) -> Result { + if b.is_empty() { + // TODO: bad error type since this branch could be reached in the 'versioned' case + // after reading 1 byte already + return Err(ResponseDeserializationError::NoData); + } + + let response_flag = ResponseFlag::try_from(b[0])?; + match response_flag { + ResponseFlag::NetworkData => Ok(Socks5ResponseContent::NetworkData( + NetworkData::try_from_bytes(&b[1..])?, + )), + ResponseFlag::ConnectionError => Ok(Socks5ResponseContent::ConnectionError( + ConnectionError::try_from_bytes(&b[1..])?, + )), + } + } +} + +/// A remote network network data response retrieved by the Socks5 service provider. This /// can be serialized and sent back through the mixnet to the requesting /// application. #[derive(Debug)] -pub struct Response { +pub struct NetworkData { pub data: Vec, pub connection_id: ConnectionId, pub is_closed: bool, } -impl Response { +impl NetworkData { /// Constructor for responses pub fn new(connection_id: ConnectionId, data: Vec, is_closed: bool) -> Self { - Response { + NetworkData { data, connection_id, is_closed, } } - pub fn try_from_bytes(b: &[u8]) -> Result { + pub fn new_closed_empty(connection_id: ConnectionId) -> Self { + NetworkData { + data: vec![], + connection_id, + is_closed: false, + } + } + + pub fn try_from_bytes(b: &[u8]) -> Result { if b.is_empty() { - return Err(ResponseError::NoData); + return Err(ResponseDeserializationError::NoData); } let is_closed = b[0] != 0; if b.len() < 9 { - return Err(ResponseError::ConnectionIdTooShort); + return Err(ResponseDeserializationError::ConnectionIdTooShort); } let mut connection_id_bytes = b.to_vec(); @@ -57,7 +250,7 @@ impl Response { connection_id_bytes[8], ]); - let response = Response::new(connection_id, data, is_closed); + let response = NetworkData::new(connection_id, data, is_closed); Ok(response) } @@ -71,54 +264,154 @@ impl Response { } } -#[cfg(test)] -mod constructing_socks5_responses_from_bytes { - use super::*; +#[derive(Debug)] +pub struct ConnectionError { + pub connection_id: ConnectionId, + pub network_requester_error: String, +} - #[test] - fn fails_when_zero_bytes_are_supplied() { - let response_bytes = Vec::new(); - - assert_eq!( - ResponseError::NoData, - Response::try_from_bytes(&response_bytes).unwrap_err() - ); +impl ConnectionError { + pub fn new(connection_id: ConnectionId, network_requester_error: String) -> Self { + ConnectionError { + connection_id, + network_requester_error, + } } - #[test] - fn fails_when_connection_id_bytes_are_too_short() { - let response_bytes = vec![0, 1, 2, 3, 4, 5, 6]; - assert_eq!( - ResponseError::ConnectionIdTooShort, - Response::try_from_bytes(&response_bytes).unwrap_err() - ); + pub fn try_from_bytes(b: &[u8]) -> Result { + if b.is_empty() { + return Err(ResponseDeserializationError::NoData); + } + + if b.len() < 8 { + return Err(ResponseDeserializationError::ConnectionIdTooShort); + } + + let mut connection_id_bytes = b.to_vec(); + let network_requester_error_bytes = connection_id_bytes.split_off(8); + + let connection_id = u64::from_be_bytes([ + connection_id_bytes[0], + connection_id_bytes[1], + connection_id_bytes[2], + connection_id_bytes[3], + connection_id_bytes[4], + connection_id_bytes[5], + connection_id_bytes[6], + connection_id_bytes[7], + ]); + let network_requester_error = String::from_utf8(network_requester_error_bytes)?; + + Ok(ConnectionError { + connection_id, + network_requester_error, + }) } - #[test] - fn works_when_there_is_no_data() { - let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7]; - let expected = Response::new( - u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]), - Vec::new(), - false, - ); - let actual = Response::try_from_bytes(&response_bytes).unwrap(); - assert_eq!(expected.connection_id, actual.connection_id); - assert_eq!(expected.data, actual.data); - assert_eq!(expected.is_closed, actual.is_closed); - } - - #[test] - fn works_when_there_is_data() { - let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255]; - let expected = Response::new( - u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]), - vec![255, 255, 255], - false, - ); - let actual = Response::try_from_bytes(&response_bytes).unwrap(); - assert_eq!(expected.connection_id, actual.connection_id); - assert_eq!(expected.data, actual.data); - assert_eq!(expected.is_closed, actual.is_closed); + pub fn into_bytes(self) -> Vec { + self.connection_id + .to_be_bytes() + .iter() + .copied() + .chain(self.network_requester_error.into_bytes().into_iter()) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(test)] + mod constructing_socks5_data_responses_from_bytes { + use super::*; + + #[test] + fn fails_when_zero_bytes_are_supplied() { + let response_bytes = Vec::new(); + + assert_eq!( + ResponseDeserializationError::NoData, + NetworkData::try_from_bytes(&response_bytes).unwrap_err() + ); + } + + #[test] + fn fails_when_connection_id_bytes_are_too_short() { + let response_bytes = vec![0, 1, 2, 3, 4, 5, 6]; + assert_eq!( + ResponseDeserializationError::ConnectionIdTooShort, + NetworkData::try_from_bytes(&response_bytes).unwrap_err() + ); + } + + #[test] + fn works_when_there_is_no_data() { + let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7]; + let expected = NetworkData::new( + u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]), + Vec::new(), + false, + ); + let actual = NetworkData::try_from_bytes(&response_bytes).unwrap(); + assert_eq!(expected.connection_id, actual.connection_id); + assert_eq!(expected.data, actual.data); + assert_eq!(expected.is_closed, actual.is_closed); + } + + #[test] + fn works_when_there_is_data() { + let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255]; + let expected = NetworkData::new( + u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]), + vec![255, 255, 255], + false, + ); + let actual = NetworkData::try_from_bytes(&response_bytes).unwrap(); + assert_eq!(expected.connection_id, actual.connection_id); + assert_eq!(expected.data, actual.data); + assert_eq!(expected.is_closed, actual.is_closed); + } + } + + #[cfg(test)] + mod connection_error_response_serde_tests { + use super::*; + + #[test] + fn simple_serde() { + let conn_id = 42; + let network_requester_error = String::from("This is a test msg"); + let response = ConnectionError::new(conn_id, network_requester_error.clone()); + let bytes = response.into_bytes(); + let deserialized_response = ConnectionError::try_from_bytes(&bytes).unwrap(); + + assert_eq!(conn_id, deserialized_response.connection_id); + assert_eq!( + network_requester_error, + deserialized_response.network_requester_error + ); + } + + #[test] + fn deserialization_errors() { + let err = ConnectionError::try_from_bytes(&[]).err().unwrap(); + assert_eq!(err, ResponseDeserializationError::NoData); + + let bytes: [u8; 5] = [1, 2, 3, 4, 5]; + let err = ConnectionError::try_from_bytes(&bytes).err().unwrap(); + assert_eq!(err, ResponseDeserializationError::ConnectionIdTooShort); + + let bytes: Vec = 42u64 + .to_be_bytes() + .into_iter() + .chain([0, 159, 146, 150].into_iter()) + .collect(); + let err = ConnectionError::try_from_bytes(&bytes).err().unwrap(); + assert!(matches!( + err, + ResponseDeserializationError::MalformedErrorMessage { .. } + )); + } } } diff --git a/common/socks5/requests/src/version.rs b/common/socks5/requests/src/version.rs new file mode 100644 index 0000000000..7bace529de --- /dev/null +++ b/common/socks5/requests/src/version.rs @@ -0,0 +1,21 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_service_providers_common::{define_simple_version, interface::Version}; + +/// Defines initial version of the communication interface between socks5 clients and +/// network requesters (socks5). +// note: we start from '3' so that we could distinguish cases where no version is provided +// and legacy communication mode is used instead +pub const INITIAL_INTERFACE_VERSION: u8 = 3; + +/// Defines the current version of the communication interface between socks5 clients and +/// network requesters (socks5). +/// It has to be incremented for any breaking change. +pub const INTERFACE_VERSION: u8 = 3; + +define_simple_version!( + Socks5ProtocolVersion, + INITIAL_INTERFACE_VERSION, + INTERFACE_VERSION +); diff --git a/common/statistics/Cargo.toml b/common/statistics/Cargo.toml index 4b0a27dcae..29573f1e06 100644 --- a/common/statistics/Cargo.toml +++ b/common/statistics/Cargo.toml @@ -2,18 +2,18 @@ # SPDX-License-Identifier: Apache-2.0 [package] -name = "statistics-common" +name = "nym-statistics-common" version = "1.0.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait = { version = "0.1.51" } -log = "0.4" +async-trait = { workspace = true } +log = { workspace = true } reqwest = { version = "0.11", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1" sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "chrono"]} thiserror = "1" -tokio = { version = "1.21.2", features = [ "time" ] } +tokio = { version = "1.24.1", features = [ "time" ] } diff --git a/common/statistics/src/api.rs b/common/statistics/src/api.rs index 6f43436432..57c0f6f2c6 100644 --- a/common/statistics/src/api.rs +++ b/common/statistics/src/api.rs @@ -5,7 +5,7 @@ use crate::error::StatsError; use crate::StatsMessage; pub const DEFAULT_STATISTICS_SERVICE_ADDRESS: &str = "127.0.0.1"; -pub const DEFAULT_STATISTICS_SERVICE_PORT: u16 = 8090; +pub const DEFAULT_STATISTICS_SERVICE_PORT: u16 = 8091; pub const STATISTICS_SERVICE_VERSION: &str = "/v1"; pub const STATISTICS_SERVICE_API_STATISTICS: &str = "statistic"; @@ -16,8 +16,7 @@ pub async fn build_and_send_statistics_request( ) -> Result<(), StatsError> { reqwest::Client::new() .post(format!( - "{}{}/{}", - url, STATISTICS_SERVICE_VERSION, STATISTICS_SERVICE_API_STATISTICS + "{url}{STATISTICS_SERVICE_VERSION}/{STATISTICS_SERVICE_API_STATISTICS}" )) .json(&msg) .send() @@ -32,11 +31,7 @@ pub fn build_statistics_request_bytes(msg: StatsMessage) -> Result, Stat let req = reqwest::Request::new( reqwest::Method::POST, reqwest::Url::parse(&format!( - "http://{}:{}{}/{}", - DEFAULT_STATISTICS_SERVICE_ADDRESS, - DEFAULT_STATISTICS_SERVICE_PORT, - STATISTICS_SERVICE_VERSION, - STATISTICS_SERVICE_API_STATISTICS + "http://{DEFAULT_STATISTICS_SERVICE_ADDRESS}:{DEFAULT_STATISTICS_SERVICE_PORT}{STATISTICS_SERVICE_VERSION}/{STATISTICS_SERVICE_API_STATISTICS}" )) .unwrap(), ); diff --git a/common/task/Cargo.toml b/common/task/Cargo.toml index ee83423f5f..7e94b5442b 100644 --- a/common/task/Cargo.toml +++ b/common/task/Cargo.toml @@ -1,19 +1,21 @@ [package] -name = "task" +name = "nym-task" version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +description = "Task handling" +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true [dependencies] futures = "0.3" -log = "0.4" +log = { workspace = true } thiserror = "1.0.37" -tokio = { version = "1.21.2", features = ["macros", "sync"] } +tokio = { version = "1.24.1", features = ["macros", "sync"] } [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] -version = "1.21.2" -features = ["signal", "time"] +version = "1.24.1" +features = ["signal", "time", "rt"] [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures] version = "0.4" @@ -22,4 +24,4 @@ version = "0.4" version = "0.2.83" [dev-dependencies] -tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal", "test-util", "macros"] } +tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "test-util", "macros"] } diff --git a/common/client-connections/src/lib.rs b/common/task/src/connections.rs similarity index 89% rename from common/client-connections/src/lib.rs rename to common/task/src/connections.rs index f86270da63..af13111526 100644 --- a/common/client-connections/src/lib.rs +++ b/common/task/src/connections.rs @@ -1,9 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::collections::HashMap; - use futures::channel::mpsc; +use std::collections::HashMap; pub type ConnectionId = u64; @@ -26,12 +25,6 @@ pub enum ConnectionCommand { // Announce that at a connection was closed. E.g the `OutQueueControl` uses this to discard // transmission lanes. Close(ConnectionId), - - // In the network requester for example, we usually want to broadcast active connections - // regularly, so we know what connections we need to request lane queue lengths for from the - // client. - // In the socks5-client, this is not needed since have direct access to the lane queue lengths. - ActiveConnections(Vec), } // The `OutQueueControl` publishes the backlog per lane, primarily so that upstream can slow down diff --git a/common/task/src/lib.rs b/common/task/src/lib.rs index 5d3d0ea2f5..b719da3a6f 100644 --- a/common/task/src/lib.rs +++ b/common/task/src/lib.rs @@ -1,14 +1,14 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub mod shutdown; +pub mod connections; +pub mod manager; #[cfg(not(target_arch = "wasm32"))] pub mod signal; pub mod spawn; -// WIP(JON): those both need to be public? -pub use shutdown::{ShutdownListener, ShutdownNotifier, StatusReceiver, StatusSender}; +pub use manager::{StatusReceiver, StatusSender, TaskClient, TaskManager}; #[cfg(not(target_arch = "wasm32"))] -pub use signal::{wait_for_signal, wait_for_signal_and_error}; +pub use signal::wait_for_signal_and_error; pub use spawn::spawn_with_report_error; diff --git a/common/task/src/shutdown.rs b/common/task/src/manager.rs similarity index 76% rename from common/task/src/shutdown.rs rename to common/task/src/manager.rs index 09e8abcb50..f16cabb54b 100644 --- a/common/task/src/shutdown.rs +++ b/common/task/src/manager.rs @@ -1,6 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::future::Future; use std::{error::Error, time::Duration}; use futures::{future::pending, FutureExt, SinkExt, StreamExt}; @@ -14,7 +15,6 @@ use tokio::{ const DEFAULT_SHUTDOWN_TIMER_SECS: u64 = 5; -// WIP(JON): can these be from futures channel too? pub(crate) type SentError = Box; type ErrorSender = mpsc::UnboundedSender; type ErrorReceiver = mpsc::UnboundedReceiver; @@ -29,9 +29,17 @@ enum TaskError { UnexpectedHalt, } -/// Used to notify other tasks to gracefully shutdown +// TODO: possibly we should create a `Status` trait instead of reusing `Error` +#[derive(thiserror::Error, Debug)] +pub enum TaskStatus { + #[error("Ready")] + Ready, +} + +/// Listens to status and error messages from tasks, as well as notifying them to gracefully +/// shutdown. Keeps track of if task stop unexpectedly, such as in a panic. #[derive(Debug)] -pub struct ShutdownNotifier { +pub struct TaskManager { // These channels have the dual purpose of signalling it's time to shutdown, but also to keep // track of which tasks we are still waiting for. notify_tx: watch::Sender<()>, @@ -55,7 +63,7 @@ pub struct ShutdownNotifier { task_status_rx: Option, } -impl Default for ShutdownNotifier { +impl Default for TaskManager { fn default() -> Self { let (notify_tx, notify_rx) = watch::channel(()); let (task_halt_tx, task_halt_rx) = mpsc::unbounded_channel(); @@ -77,11 +85,7 @@ impl Default for ShutdownNotifier { } } -impl ShutdownNotifier { - pub fn take_task_status_rx(&mut self) -> Option { - self.task_status_rx.take() - } - +impl TaskManager { pub fn new(shutdown_timer_secs: u64) -> Self { Self { shutdown_timer_secs, @@ -89,8 +93,21 @@ impl ShutdownNotifier { } } - pub fn subscribe(&self) -> ShutdownListener { - ShutdownListener::new( + #[cfg(not(target_arch = "wasm32"))] + pub async fn catch_interrupt(mut self) -> Result<(), SentError> { + let res = crate::wait_for_signal_and_error(&mut self).await; + + log::info!("Sending shutdown"); + self.signal_shutdown().ok(); + + log::info!("Waiting for tasks to finish... (Press ctrl-c to force)"); + self.wait_for_shutdown().await; + + res + } + + pub fn subscribe(&self) -> TaskClient { + TaskClient::new( self.notify_rx .as_ref() .expect("Unable to subscribe to shutdown notifier that is already shutdown") @@ -105,15 +122,21 @@ impl ShutdownNotifier { self.notify_tx.send(()) } - pub fn start_status_listener(&mut self, mut sender: StatusSender) { + pub async fn start_status_listener(&mut self, mut sender: StatusSender) { + // Announce that we are operational. This means that in the application where this is used, + // everything is up and running and ready to go. + if let Err(msg) = sender.send(Box::new(TaskStatus::Ready)).await { + log::error!("Error sending status message: {}", msg); + }; + if let Some(mut task_status_rx) = self.task_status_rx.take() { log::info!("Starting status message listener"); crate::spawn::spawn(async move { loop { if let Some(msg) = task_status_rx.next().await { log::trace!("Got msg: {}", msg); - if sender.send(msg).await.is_err() { - log::error!("Error sending status message"); + if let Err(msg) = sender.send(msg).await { + log::error!("Error sending status message: {}", msg); } } else { log::trace!("Stopping since channel closed"); @@ -149,7 +172,7 @@ impl ShutdownNotifier { } pub async fn wait_for_shutdown(&mut self) { - log::info!("Waiting for shutdown"); + log::debug!("Waiting for shutdown"); if let Some(notify_rx) = self.notify_rx.take() { drop(notify_rx); } @@ -173,9 +196,10 @@ impl ShutdownNotifier { } } -/// Listen for shutdown notifications +/// Listen for shutdown notifications, and can send error and status messages back to the +/// `TaskManager` #[derive(Clone, Debug)] -pub struct ShutdownListener { +pub struct TaskClient { // If a shutdown notification has been registered shutdown: bool, @@ -193,43 +217,53 @@ pub struct ShutdownListener { status_msg: StatusSender, // The current operating mode - mode: ShutdownListenerMode, + mode: ClientOperatingMode, } -impl ShutdownListener { +impl TaskClient { #[cfg(not(target_arch = "wasm32"))] - const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); + const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(5); fn new( notify: watch::Receiver<()>, return_error: ErrorSender, drop_error: ErrorSender, status_msg: StatusSender, - ) -> ShutdownListener { - ShutdownListener { + ) -> TaskClient { + TaskClient { shutdown: false, notify, return_error, drop_error, status_msg, - mode: ShutdownListenerMode::Listening, + mode: ClientOperatingMode::Listening, + } + } + + pub async fn run_future(&mut self, fut: Fut) -> Option + where + Fut: Future, + { + tokio::select! { + biased; + _ = self.recv() => None, + res = fut => Some(res) } } // Create a dummy that will never report that we should shutdown. - pub fn dummy() -> ShutdownListener { + pub fn dummy() -> TaskClient { let (_notify_tx, notify_rx) = watch::channel(()); let (task_halt_tx, _task_halt_rx) = mpsc::unbounded_channel(); let (task_drop_tx, _task_drop_rx) = mpsc::unbounded_channel(); - //let (task_status_tx, _task_status_rx) = futures::channel::mpsc::unbounded(); let (task_status_tx, _task_status_rx) = futures::channel::mpsc::channel(128); - ShutdownListener { + TaskClient { shutdown: false, notify: notify_rx, return_error: task_halt_tx, drop_error: task_drop_tx, status_msg: task_status_tx, - mode: ShutdownListenerMode::Dummy, + mode: ClientOperatingMode::Dummy, } } @@ -256,14 +290,26 @@ impl ShutdownListener { self.shutdown = true; } + pub async fn recv_with_delay(&mut self) { + self.recv() + .then(|msg| async move { + sleep(Duration::from_secs(2)).await; + msg + }) + .await + } + pub async fn recv_timeout(&mut self) { if self.mode.is_dummy() { return pending().await; } #[cfg(not(target_arch = "wasm32"))] - tokio::time::timeout(Self::SHUTDOWN_TIMEOUT, self.recv()) - .await - .expect("Task stopped without shutdown called"); + tokio::time::timeout( + Self::SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT, + self.recv(), + ) + .await + .expect("Task stopped without shutdown called"); } pub fn is_shutdown_poll(&mut self) -> bool { @@ -281,8 +327,8 @@ impl ShutdownListener { has_changed } Err(err) => { - log::debug!("Polling shutdown failed: {err}"); - log::debug!("Assuming this means we should shutdown..."); + log::error!("Polling shutdown failed: {err}"); + log::error!("Assuming this means we should shutdown..."); true } } @@ -299,7 +345,7 @@ impl ShutdownListener { if self.mode.is_dummy() { return; } - log::trace!("Notifying we stopped: {:?}", err); + log::trace!("Notifying we stopped: {err}"); if self.return_error.send(err).is_err() { log::error!("Failed to send back error message"); } @@ -315,7 +361,7 @@ impl ShutdownListener { } } -impl Drop for ShutdownListener { +impl Drop for TaskClient { fn drop(&mut self) { if !self.mode.should_signal_on_drop() { return; @@ -331,7 +377,7 @@ impl Drop for ShutdownListener { } #[derive(Clone, Debug, PartialEq, Eq)] -enum ShutdownListenerMode { +enum ClientOperatingMode { // Normal operations Listening, // Normal operations, but we don't report back if the we stop by getting dropped. @@ -340,20 +386,20 @@ enum ShutdownListenerMode { Dummy, } -impl ShutdownListenerMode { +impl ClientOperatingMode { fn is_dummy(&self) -> bool { - self == &ShutdownListenerMode::Dummy + self == &ClientOperatingMode::Dummy } fn should_signal_on_drop(&self) -> bool { match self { - ShutdownListenerMode::Listening => true, - ShutdownListenerMode::ListeningButDontReportHalt | ShutdownListenerMode::Dummy => false, + ClientOperatingMode::Listening => true, + ClientOperatingMode::ListeningButDontReportHalt | ClientOperatingMode::Dummy => false, } } fn set_should_not_signal_on_drop(&mut self) { - use ShutdownListenerMode::{Dummy, Listening, ListeningButDontReportHalt}; + use ClientOperatingMode::{Dummy, Listening, ListeningButDontReportHalt}; *self = match &self { ListeningButDontReportHalt | Listening => ListeningButDontReportHalt, Dummy => Dummy, @@ -367,7 +413,7 @@ mod tests { #[tokio::test] async fn signal_shutdown() { - let shutdown = ShutdownNotifier::default(); + let shutdown = TaskManager::default(); let mut listener = shutdown.subscribe(); let task = tokio::spawn(async move { diff --git a/common/task/src/signal.rs b/common/task/src/signal.rs index a04a0f5601..e3fb3786a2 100644 --- a/common/task/src/signal.rs +++ b/common/task/src/signal.rs @@ -1,6 +1,7 @@ -use crate::{shutdown::SentError, ShutdownNotifier}; +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"); @@ -29,7 +30,7 @@ pub async fn wait_for_signal() { } #[cfg(unix)] -pub async fn wait_for_signal_and_error(shutdown: &mut ShutdownNotifier) -> Result<(), SentError> { +pub async fn wait_for_signal_and_error(shutdown: &mut TaskManager) -> Result<(), SentError> { use tokio::signal::unix::{signal, SignalKind}; let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel"); @@ -56,7 +57,7 @@ pub async fn wait_for_signal_and_error(shutdown: &mut ShutdownNotifier) -> Resul } #[cfg(not(unix))] -pub async fn wait_for_signal_and_error(shutdown: &mut ShutdownNotifier) -> Result<(), SentError> { +pub async fn wait_for_signal_and_error(shutdown: &mut TaskManager) -> Result<(), SentError> { tokio::select! { _ = tokio::signal::ctrl_c() => { log::info!("Received SIGINT"); diff --git a/common/task/src/spawn.rs b/common/task/src/spawn.rs index ba66ea7f16..d30c9ac850 100644 --- a/common/task/src/spawn.rs +++ b/common/task/src/spawn.rs @@ -1,4 +1,4 @@ -use crate::ShutdownListener; +use crate::TaskClient; use std::future::Future; #[cfg(target_arch = "wasm32")] @@ -18,7 +18,7 @@ where tokio::spawn(future); } -pub fn spawn_with_report_error(future: F, mut shutdown: ShutdownListener) +pub fn spawn_with_report_error(future: F, mut shutdown: TaskClient) where F: Future> + Send + 'static, T: 'static, diff --git a/common/topology/Cargo.toml b/common/topology/Cargo.toml index 2996aeb639..fbb5985ad5 100644 --- a/common/topology/Cargo.toml +++ b/common/topology/Cargo.toml @@ -1,20 +1,30 @@ [package] -name = "topology" +name = "nym-topology" version = "0.1.0" -authors = ["Jedrzej Stuczynski "] -edition = "2021" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +readme = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bs58 = "0.4" -log = "0.4" +log = { workspace = true } rand = { version = "0.7.3", features = ["wasm-bindgen"] } thiserror = "1.0.37" +async-trait = { workspace = true, optional = true } ## internal -crypto = { path = "../crypto" } -mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } -nymsphinx-addressing = { path = "../nymsphinx/addressing" } -nymsphinx-types = { path = "../nymsphinx/types" } -version-checker = { path = "../version-checker" } +nym-crypto = { path = "../crypto" } +nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } +nym-sphinx-addressing = { path = "../nymsphinx/addressing" } +nym-sphinx-types = { path = "../nymsphinx/types" } +nym-bin-common = { path = "../bin-common" } + +[features] +default = ["provider-trait"] +provider-trait = ["async-trait"] \ No newline at end of file diff --git a/common/topology/src/filter.rs b/common/topology/src/filter.rs index 6c1a3c2fe1..4d8d977d9e 100644 --- a/common/topology/src/filter.rs +++ b/common/topology/src/filter.rs @@ -1,6 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_bin_common::version_checker; use std::collections::HashMap; use std::hash::Hash; diff --git a/common/topology/src/gateway.rs b/common/topology/src/gateway.rs index 57b34146e6..8a2a424925 100644 --- a/common/topology/src/gateway.rs +++ b/common/topology/src/gateway.rs @@ -2,68 +2,30 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{filter, NetworkAddress}; -use crypto::asymmetric::{encryption, identity}; -use mixnet_contract_common::GatewayBond; -use nymsphinx_addressing::nodes::{NodeIdentity, NymNodeRoutingAddress}; -use nymsphinx_types::Node as SphinxNode; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_mixnet_contract_common::GatewayBond; +use nym_sphinx_addressing::nodes::{NodeIdentity, NymNodeRoutingAddress}; +use nym_sphinx_types::Node as SphinxNode; use std::convert::{TryFrom, TryInto}; -use std::fmt::{self, Display, Formatter}; +use std::fmt; use std::io; use std::net::SocketAddr; +use thiserror::Error; -#[derive(Debug)] +#[derive(Error, Debug)] pub enum GatewayConversionError { - InvalidIdentityKey(identity::Ed25519RecoveryError), - InvalidSphinxKey(encryption::KeyRecoveryError), - InvalidAddress(String, io::Error), - InvalidStake, - Other(Box), -} + #[error("gateway identity key was malformed - {0}")] + InvalidIdentityKey(#[from] identity::Ed25519RecoveryError), -impl From for GatewayConversionError { - fn from(err: encryption::KeyRecoveryError) -> Self { - GatewayConversionError::InvalidSphinxKey(err) - } -} + #[error("gateway sphinx key was malformed - {0}")] + InvalidSphinxKey(#[from] encryption::KeyRecoveryError), -impl From for GatewayConversionError { - fn from(err: identity::Ed25519RecoveryError) -> Self { - GatewayConversionError::InvalidIdentityKey(err) - } -} - -impl Display for GatewayConversionError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - GatewayConversionError::InvalidIdentityKey(err) => write!( - f, - "failed to convert gateway due to invalid identity key - {}", - err - ), - GatewayConversionError::InvalidSphinxKey(err) => write!( - f, - "failed to convert gateway due to invalid sphinx key - {}", - err - ), - GatewayConversionError::InvalidAddress(address, err) => { - write!( - f, - "failed to convert gateway due to invalid address {} - {}", - address, err - ) - } - GatewayConversionError::InvalidStake => { - write!(f, "failed to convert gateway due to invalid stake") - } - GatewayConversionError::Other(err) => { - write!( - f, - "failed to convert gateway due to another error - {}", - err - ) - } - } - } + #[error("'{value}' is not a valid gateway address - {source}")] + InvalidAddress { + value: String, + #[source] + source: io::Error, + }, } #[derive(Debug, Clone)] @@ -123,14 +85,22 @@ impl<'a> TryFrom<&'a GatewayBond> for Node { type Error = GatewayConversionError; fn try_from(bond: &'a GatewayBond) -> Result { - let host: NetworkAddress = bond.gateway.host.parse().map_err(|err| { - GatewayConversionError::InvalidAddress(bond.gateway.host.clone(), err) - })?; + let host: NetworkAddress = + bond.gateway + .host + .parse() + .map_err(|err| GatewayConversionError::InvalidAddress { + value: bond.gateway.host.clone(), + source: err, + })?; // try to completely resolve the host in the mix situation to avoid doing it every // single time we want to construct a path let mix_host = host.to_socket_addrs(bond.gateway.mix_port).map_err(|err| { - GatewayConversionError::InvalidAddress(bond.gateway.host.clone(), err) + GatewayConversionError::InvalidAddress { + value: bond.gateway.host.clone(), + source: err, + } })?[0]; Ok(Node { diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index ff3ba55c83..4eab444605 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -3,11 +3,11 @@ use crate::filter::VersionFilterable; use log::warn; -use mixnet_contract_common::mixnode::MixNodeDetails; -use mixnet_contract_common::GatewayBond; -use nymsphinx_addressing::nodes::NodeIdentity; -use nymsphinx_types::Node as SphinxNode; -use rand::Rng; +use nym_mixnet_contract_common::mixnode::MixNodeDetails; +use nym_mixnet_contract_common::GatewayBond; +use nym_sphinx_addressing::nodes::NodeIdentity; +use nym_sphinx_types::Node as SphinxNode; +use rand::{CryptoRng, Rng}; use std::collections::HashMap; use std::convert::TryInto; use std::fmt::{self, Display, Formatter}; @@ -20,6 +20,9 @@ pub mod filter; pub mod gateway; pub mod mix; +#[cfg(feature = "provider-trait")] +pub mod provider_trait; + #[derive(Debug, Clone, Error)] pub enum NymTopologyError { #[error("The provided network topology is empty - there are no mixnodes and no gateways on it - the network request(s) probably failed")] @@ -39,6 +42,16 @@ pub enum NymTopologyError { #[error("No mixnodes available on layer {layer}")] EmptyMixLayer { layer: MixLayer }, + + #[error("Uneven layer distribution. Layer {layer} has {nodes} on it, while we expected a value between {lower_bound} and {upper_bound} as we have {total_nodes} nodes in total. Full breakdown: {layer_distribution:?}")] + UnevenLayerDistribution { + layer: MixLayer, + nodes: usize, + lower_bound: usize, + upper_bound: usize, + total_nodes: usize, + layer_distribution: Vec<(MixLayer, usize)>, + }, } #[derive(Debug, Clone)] @@ -97,7 +110,7 @@ impl NymTopology { } pub fn num_mixnodes(&self) -> usize { - self.mixes.values().flat_map(|m| m.iter()).count() + self.mixes.values().map(|m| m.len()).sum() } pub fn mixes_as_vec(&self) -> Vec { @@ -181,8 +194,7 @@ impl NymTopology { gateway_identity: &NodeIdentity, ) -> Result, NymTopologyError> where - // I don't think there's a need for this RNG to be crypto-secure - R: Rng + ?Sized, + R: Rng + CryptoRng + ?Sized, { let gateway = self.get_gateway(gateway_identity).ok_or( NymTopologyError::NonExistentGatewayError { @@ -238,6 +250,46 @@ impl NymTopology { Ok(()) } + pub fn ensure_even_layer_distribution( + &self, + lower_threshold: f32, + upper_threshold: f32, + ) -> Result<(), NymTopologyError> { + let mixnodes_count = self.num_mixnodes(); + + let layers = self + .mixes + .iter() + .map(|(k, v)| (*k, v.len())) + .collect::>(); + + if self.gateways.is_empty() { + return Err(NymTopologyError::NoGatewaysAvailable); + } + + if layers.is_empty() { + return Err(NymTopologyError::NoMixnodesAvailable); + } + + let upper_bound = (mixnodes_count as f32 * upper_threshold) as usize; + let lower_bound = (mixnodes_count as f32 * lower_threshold) as usize; + + for (layer, nodes) in &layers { + if nodes < &lower_bound || nodes > &upper_bound { + return Err(NymTopologyError::UnevenLayerDistribution { + layer: *layer, + nodes: *nodes, + lower_bound, + upper_bound, + total_nodes: mixnodes_count, + layer_distribution: layers, + }); + } + } + + Ok(()) + } + #[must_use] pub fn filter_system_version(&self, expected_version: &str) -> Self { self.filter_node_versions(expected_version) @@ -276,7 +328,7 @@ pub fn nym_topology_from_detailed( match bond.try_into() { Ok(mix) => layer_entry.push(mix), Err(err) => { - warn!("Mix {} / {} is malformed - {}", mix_id, mix_identity, err); + warn!("Mix {} / {} is malformed - {err}", mix_id, mix_identity); continue; } } @@ -288,7 +340,7 @@ pub fn nym_topology_from_detailed( match bond.try_into() { Ok(gate) => gateways.push(gate), Err(err) => { - warn!("Gateway {} is malformed - {}", gate_id, err); + warn!("Gateway {} is malformed - {err}", gate_id); continue; } } @@ -303,10 +355,10 @@ mod converting_mixes_to_vec { #[cfg(test)] mod when_nodes_exist { - use crypto::asymmetric::{encryption, identity}; + use nym_crypto::asymmetric::{encryption, identity}; use super::*; - use mixnet_contract_common::Layer; + use nym_mixnet_contract_common::Layer; #[test] fn returns_a_vec_with_hashmap_values() { diff --git a/common/topology/src/mix.rs b/common/topology/src/mix.rs index 81e0308b2f..14dcc02857 100644 --- a/common/topology/src/mix.rs +++ b/common/topology/src/mix.rs @@ -2,68 +2,30 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{filter, NetworkAddress}; -use crypto::asymmetric::{encryption, identity}; -use mixnet_contract_common::{Layer, MixId, MixNodeBond}; -use nymsphinx_addressing::nodes::NymNodeRoutingAddress; -use nymsphinx_types::Node as SphinxNode; +use nym_crypto::asymmetric::{encryption, identity}; +pub use nym_mixnet_contract_common::Layer; +use nym_mixnet_contract_common::{MixId, MixNodeBond}; +use nym_sphinx_addressing::nodes::NymNodeRoutingAddress; +use nym_sphinx_types::Node as SphinxNode; use std::convert::{TryFrom, TryInto}; -use std::fmt::{self, Display, Formatter}; use std::io; use std::net::SocketAddr; +use thiserror::Error; -#[derive(Debug)] +#[derive(Error, Debug)] pub enum MixnodeConversionError { - InvalidIdentityKey(identity::Ed25519RecoveryError), - InvalidSphinxKey(encryption::KeyRecoveryError), - InvalidAddress(String, io::Error), - InvalidStake, - Other(Box), -} + #[error("mixnode identity key was malformed - {0}")] + InvalidIdentityKey(#[from] identity::Ed25519RecoveryError), -impl From for MixnodeConversionError { - fn from(err: encryption::KeyRecoveryError) -> Self { - MixnodeConversionError::InvalidSphinxKey(err) - } -} + #[error("mixnode sphinx key was malformed - {0}")] + InvalidSphinxKey(#[from] encryption::KeyRecoveryError), -impl From for MixnodeConversionError { - fn from(err: identity::Ed25519RecoveryError) -> Self { - MixnodeConversionError::InvalidIdentityKey(err) - } -} - -impl Display for MixnodeConversionError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - MixnodeConversionError::InvalidIdentityKey(err) => write!( - f, - "failed to convert mixnode due to invalid identity key - {}", - err - ), - MixnodeConversionError::InvalidSphinxKey(err) => write!( - f, - "failed to convert mixnode due to invalid sphinx key - {}", - err - ), - MixnodeConversionError::InvalidAddress(address, err) => { - write!( - f, - "failed to convert mixnode due to invalid address {} - {}", - address, err - ) - } - MixnodeConversionError::InvalidStake => { - write!(f, "failed to convert mixnode due to invalid stake") - } - MixnodeConversionError::Other(err) => { - write!( - f, - "failed to convert mixnode due to another error - {}", - err - ) - } - } - } + #[error("'{value}' is not a valid mixnode address - {source}")] + InvalidAddress { + value: String, + #[source] + source: io::Error, + }, } #[derive(Debug, Clone)] @@ -100,16 +62,22 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node { type Error = MixnodeConversionError; fn try_from(bond: &'a MixNodeBond) -> Result { - let host: NetworkAddress = bond.mix_node.host.parse().map_err(|err| { - MixnodeConversionError::InvalidAddress(bond.mix_node.host.clone(), err) - })?; + let host: NetworkAddress = + bond.mix_node + .host + .parse() + .map_err(|err| MixnodeConversionError::InvalidAddress { + value: bond.mix_node.host.clone(), + source: err, + })?; // try to completely resolve the host in the mix situation to avoid doing it every // single time we want to construct a path let mix_host = host .to_socket_addrs(bond.mix_node.mix_port) - .map_err(|err| { - MixnodeConversionError::InvalidAddress(bond.mix_node.host.clone(), err) + .map_err(|err| MixnodeConversionError::InvalidAddress { + value: bond.mix_node.host.clone(), + source: err, })?[0]; Ok(Node { diff --git a/common/topology/src/provider_trait.rs b/common/topology/src/provider_trait.rs new file mode 100644 index 0000000000..562cfaa5f7 --- /dev/null +++ b/common/topology/src/provider_trait.rs @@ -0,0 +1,44 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::NymTopology; +pub use async_trait::async_trait; + +// hehe, wasm +#[cfg(not(target_arch = "wasm32"))] +#[async_trait] +pub trait TopologyProvider: Send { + async fn get_new_topology(&mut self) -> Option; +} + +#[cfg(target_arch = "wasm32")] +#[async_trait(?Send)] +pub trait TopologyProvider { + async fn get_new_topology(&mut self) -> Option; +} + +pub struct HardcodedTopologyProvider { + topology: NymTopology, +} + +impl HardcodedTopologyProvider { + pub fn new(topology: NymTopology) -> Self { + HardcodedTopologyProvider { topology } + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[async_trait] +impl TopologyProvider for HardcodedTopologyProvider { + async fn get_new_topology(&mut self) -> Option { + Some(self.topology.clone()) + } +} + +#[cfg(target_arch = "wasm32")] +#[async_trait(?Send)] +impl TopologyProvider for HardcodedTopologyProvider { + async fn get_new_topology(&mut self) -> Option { + Some(self.topology.clone()) + } +} diff --git a/common/types/Cargo.toml b/common/types/Cargo.toml index 54603c02d2..5f822c4987 100644 --- a/common/types/Cargo.toml +++ b/common/types/Cargo.toml @@ -2,13 +2,13 @@ name = "nym-types" version = "1.0.0" description = "Nym common types" -authors = ["Nym Technologies SA"] +authors.workspace = true edition = "2021" rust-version = "1.58" [dependencies] eyre = "0.6.5" -log = "0.4" +log = { workspace = true } itertools = "0.10" reqwest = "0.11.9" schemars = "0.8" @@ -19,22 +19,22 @@ thiserror = "1.0" url = "2.2" ts-rs = "6.1.2" -cosmwasm-std = "1.0.0" +cosmwasm-std = { workspace = true } cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } validator-client = { path = "../../common/client-libs/validator-client", features = [ - "nymd-client", + "nyxd-client", ] } -mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } -vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } -config = { path = "../../common/config" } -coconut-interface = { path = "../../common/coconut-interface" } +nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } +nym-config = { path = "../../common/config" } +nym-coconut-interface = { path = "../../common/coconut-interface" } # Used for Type conversion, can be extracted but its a lot of work -vesting-contract = { path = "../../contracts/vesting" } +nym-vesting-contract = { path = "../../contracts/vesting" } [dev-dependencies] tempfile = "3.3.0" [features] default = [] -generate-ts = ["mixnet-contract-common/generate-ts"] +generate-ts = ["nym-mixnet-contract-common/generate-ts"] diff --git a/common/types/src/account.rs b/common/types/src/account.rs index 47c6fc0c6d..12bc12efd4 100644 --- a/common/types/src/account.rs +++ b/common/types/src/account.rs @@ -1,5 +1,5 @@ use crate::currency::{CurrencyDenom, DecCoin}; -use config::defaults::DenomDetails; +use nym_config::defaults::DenomDetails; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/common/types/src/currency.rs b/common/types/src/currency.rs index 38292d11d3..df0a735a69 100644 --- a/common/types/src/currency.rs +++ b/common/types/src/currency.rs @@ -1,7 +1,7 @@ use crate::error::TypesError; -use config::defaults::{DenomDetails, DenomDetailsOwned, NymNetworkDetails}; use cosmwasm_std::Fraction; use cosmwasm_std::{Decimal, Uint128}; +use nym_config::defaults::{DenomDetails, DenomDetailsOwned, NymNetworkDetails}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; @@ -9,7 +9,7 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::fmt::{Display, Formatter}; use strum::{Display, EnumString, EnumVariantNames}; -use validator_client::nymd::Coin; +use validator_client::nyxd::Coin; #[cfg(feature = "generate-ts")] use ts_rs::{Dependency, TS}; diff --git a/common/types/src/delegation.rs b/common/types/src/delegation.rs index bee74dd646..399bf7b91b 100644 --- a/common/types/src/delegation.rs +++ b/common/types/src/delegation.rs @@ -3,7 +3,7 @@ use crate::deprecated::DelegationEvent; use crate::error::TypesError; use crate::mixnode::MixNodeCostParams; use cosmwasm_std::Decimal; -use mixnet_contract_common::{Delegation as MixnetContractDelegation, MixId}; +use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, MixId}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/common/types/src/deprecated.rs b/common/types/src/deprecated.rs index 62c7193c9e..87f1f04e50 100644 --- a/common/types/src/deprecated.rs +++ b/common/types/src/deprecated.rs @@ -4,7 +4,7 @@ use crate::currency::DecCoin; use crate::error::TypesError; use crate::pending_events::{PendingEpochEvent, PendingEpochEventData}; -use mixnet_contract_common::{IdentityKey, MixId}; +use nym_mixnet_contract_common::{IdentityKey, MixId}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/common/types/src/error.rs b/common/types/src/error.rs index b0874ba7aa..39248500e2 100644 --- a/common/types/src/error.rs +++ b/common/types/src/error.rs @@ -1,16 +1,16 @@ use serde::{Serialize, Serializer}; use std::io; use thiserror::Error; -use validator_client::validator_api::error::ValidatorAPIError; -use validator_client::{nymd::error::NymdError, ValidatorClientError}; +use validator_client::nym_api::error::NymAPIError; +use validator_client::{nyxd::error::NyxdError, ValidatorClientError}; // TODO: ask @MS why this even exists #[derive(Error, Debug)] pub enum TypesError { #[error("{source}")] - NymdError { + NyxdError { #[from] - source: NymdError, + source: NyxdError, }, #[error("{source}")] CosmwasmStd { @@ -23,9 +23,9 @@ pub enum TypesError { source: eyre::Report, }, #[error("{source}")] - ValidatorApiError { + NymApiError { #[from] - source: ValidatorAPIError, + source: NymAPIError, }, #[error("{source}")] IOError { @@ -52,8 +52,8 @@ pub enum TypesError { #[from] source: cosmwasm_std::DecimalRangeExceeded, }, - #[error("No validator API URL configured")] - NoValidatorApiUrlConfigured, + #[error("No nym API URL configured")] + NoNymApiUrlConfigured, #[error("{0} is not a valid amount string")] InvalidAmount(String), #[error("{0} is not a valid denomination string")] @@ -88,10 +88,10 @@ impl Serialize for TypesError { impl From for TypesError { fn from(e: ValidatorClientError) -> Self { match e { - ValidatorClientError::ValidatorAPIError { source } => source.into(), + ValidatorClientError::NymAPIError { source } => source.into(), ValidatorClientError::MalformedUrlProvided(e) => e.into(), - ValidatorClientError::NymdError(e) => e.into(), - ValidatorClientError::NoAPIUrlAvailable => TypesError::NoValidatorApiUrlConfigured, + ValidatorClientError::NyxdError(e) => e.into(), + ValidatorClientError::NoAPIUrlAvailable => TypesError::NoNymApiUrlConfigured, } } } diff --git a/common/types/src/fees.rs b/common/types/src/fees.rs index 747693ce9c..561a1a8e81 100644 --- a/common/types/src/fees.rs +++ b/common/types/src/fees.rs @@ -1,6 +1,6 @@ use crate::currency::DecCoin; use serde::{Deserialize, Serialize}; -use validator_client::nymd::Fee; +use validator_client::nyxd::Fee; #[cfg(feature = "generate-ts")] use ts_rs::{Dependency, TS}; @@ -53,7 +53,7 @@ impl TS for FeeDetails { #[cfg(feature = "generate-ts")] pub mod ts_type_helpers { use serde::{Deserialize, Serialize}; - use validator_client::nymd::GasAdjustment; + use validator_client::nyxd::GasAdjustment; #[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS)] #[ts(export_to = "ts-packages/types/src/types/rust/Fee.ts")] diff --git a/common/types/src/gas.rs b/common/types/src/gas.rs index 9441008569..23e004293b 100644 --- a/common/types/src/gas.rs +++ b/common/types/src/gas.rs @@ -1,6 +1,6 @@ use cosmrs::tx::Gas as CosmrsGas; use serde::{Deserialize, Serialize}; -use validator_client::nymd::cosmwasm_client::types::GasInfo as ValidatorClientGasInfo; +use validator_client::nyxd::cosmwasm_client::types::GasInfo as ValidatorClientGasInfo; #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( diff --git a/common/types/src/gateway.rs b/common/types/src/gateway.rs index 8895a69761..d512ef5f30 100644 --- a/common/types/src/gateway.rs +++ b/common/types/src/gateway.rs @@ -1,10 +1,11 @@ use crate::currency::{DecCoin, RegisteredCoins}; use crate::error::TypesError; -use mixnet_contract_common::{ +use nym_mixnet_contract_common::{ Gateway as MixnetContractGateway, GatewayBond as MixnetContractGatewayBond, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::fmt; #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( @@ -75,3 +76,37 @@ impl GatewayBond { }) } } + +#[derive(Serialize, Deserialize)] +pub struct GatewayNodeDetailsResponse { + pub identity_key: String, + pub sphinx_key: String, + pub owner_signature: String, + pub announce_address: String, + pub bind_address: String, + pub version: String, + pub mix_port: u16, + pub clients_port: u16, + pub data_store: String, +} + +impl fmt::Display for GatewayNodeDetailsResponse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "Identity Key: {}", self.identity_key)?; + writeln!(f, "Sphinx Key: {}", self.sphinx_key)?; + writeln!(f, "Owner Signature: {}", self.owner_signature)?; + writeln!( + f, + "Host: {} (bind address: {})", + self.announce_address, self.bind_address + )?; + writeln!(f, "Version: {}", self.version)?; + writeln!( + f, + "Mix Port: {}, Clients port: {}", + self.mix_port, self.clients_port + )?; + + writeln!(f, "Data store is at: {}", self.data_store) + } +} diff --git a/common/types/src/mixnode.rs b/common/types/src/mixnode.rs index e1c3bf4940..4518700449 100644 --- a/common/types/src/mixnode.rs +++ b/common/types/src/mixnode.rs @@ -4,7 +4,7 @@ use crate::currency::{DecCoin, RegisteredCoins}; use crate::error::TypesError; use cosmwasm_std::Decimal; -use mixnet_contract_common::{ +use nym_mixnet_contract_common::{ EpochId, MixId, MixNode, MixNodeBond as MixnetContractMixNodeBond, MixNodeCostParams as MixnetContractMixNodeCostParams, MixNodeDetails as MixnetContractMixNodeDetails, @@ -12,6 +12,7 @@ use mixnet_contract_common::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::fmt; #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( @@ -161,3 +162,38 @@ impl MixNodeCostParams { }) } } + +#[derive(Serialize, Deserialize)] +pub struct MixnodeNodeDetailsResponse { + pub identity_key: String, + pub sphinx_key: String, + pub owner_signature: String, + pub announce_address: String, + pub bind_address: String, + pub version: String, + pub mix_port: u16, + pub http_api_port: u16, + pub verloc_port: u16, + pub wallet_address: Option, +} + +impl fmt::Display for MixnodeNodeDetailsResponse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let wallet_address = self.wallet_address.clone().unwrap_or_default(); + writeln!(f, "Identity Key: {}", self.identity_key)?; + writeln!(f, "Sphinx Key: {}", self.sphinx_key)?; + writeln!(f, "Owner Signature: {}", self.owner_signature)?; + writeln!( + f, + "Host: {} (bind address: {})", + self.announce_address, self.bind_address + )?; + writeln!(f, "Version: {}", self.version)?; + writeln!( + f, + "Mix Port: {}, Verloc port: {}, Http Port: {}\n", + self.mix_port, self.verloc_port, self.http_api_port + )?; + writeln!(f, "You are bonding to wallet address: {wallet_address}\n\n") + } +} diff --git a/common/types/src/pending_events.rs b/common/types/src/pending_events.rs index e9d578dc12..8f537fc200 100644 --- a/common/types/src/pending_events.rs +++ b/common/types/src/pending_events.rs @@ -4,7 +4,7 @@ use crate::currency::{DecCoin, RegisteredCoins}; use crate::error::TypesError; use crate::mixnode::MixNodeCostParams; -use mixnet_contract_common::{ +use nym_mixnet_contract_common::{ BlockHeight, EpochEventId, IntervalEventId, IntervalRewardingParamsUpdate, MixId, PendingEpochEvent as MixnetContractPendingEpochEvent, PendingEpochEventKind as MixnetContractPendingEpochEventKind, diff --git a/common/types/src/transaction.rs b/common/types/src/transaction.rs index b5dd50a4ae..e896f95130 100644 --- a/common/types/src/transaction.rs +++ b/common/types/src/transaction.rs @@ -2,8 +2,8 @@ use crate::currency::DecCoin; use crate::error::TypesError; use crate::gas::{Gas, GasInfo}; use serde::{Deserialize, Serialize}; -use validator_client::nymd::cosmwasm_client::types::ExecuteResult; -use validator_client::nymd::TxResponse; +use validator_client::nyxd::cosmwasm_client::types::ExecuteResult; +use validator_client::nyxd::TxResponse; #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( diff --git a/common/types/src/vesting.rs b/common/types/src/vesting.rs index 0c6b6a4c20..9be8ef03da 100644 --- a/common/types/src/vesting.rs +++ b/common/types/src/vesting.rs @@ -1,10 +1,10 @@ use crate::currency::{DecCoin, RegisteredCoins}; use crate::error::TypesError; +use nym_vesting_contract_common::OriginalVestingResponse as ContractOriginalVestingResponse; +use nym_vesting_contract_common::PledgeData as ContractPledgeData; use serde::{Deserialize, Serialize}; use vesting_contract::vesting::Account as ContractVestingAccount; use vesting_contract::vesting::VestingPeriod as ContractVestingPeriod; -use vesting_contract_common::OriginalVestingResponse as ContractOriginalVestingResponse; -use vesting_contract_common::PledgeData as ContractPledgeData; #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( diff --git a/common/version-checker/Cargo.toml b/common/version-checker/Cargo.toml deleted file mode 100644 index 9bc3c16ccf..0000000000 --- a/common/version-checker/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "version-checker" -version = "0.1.0" -authors = ["Dave Hrycyszyn "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -semver = "0.11" diff --git a/common/wasm-utils/src/websocket/state.rs b/common/wasm-utils/src/websocket/state.rs index d9f0f15f76..aee1525141 100644 --- a/common/wasm-utils/src/websocket/state.rs +++ b/common/wasm-utils/src/websocket/state.rs @@ -21,7 +21,7 @@ impl From for State { WebSocket::OPEN => State::Open, WebSocket::CLOSING => State::Closing, WebSocket::CLOSED => State::Closed, - n => panic!("{} is not a valid WebSocket state!", n), // should we panic here or change it into `TryFrom` instead? + n => panic!("{n} is not a valid WebSocket state!"), // should we panic here or change it into `TryFrom` instead? } } } diff --git a/contracts/.gitignore b/contracts/.gitignore deleted file mode 100644 index ffa3bbd20e..0000000000 --- a/contracts/.gitignore +++ /dev/null @@ -1 +0,0 @@ -Cargo.lock \ No newline at end of file diff --git a/contracts/CHANGELOG.md b/contracts/CHANGELOG.md index d52edc427d..d622d922fa 100644 --- a/contracts/CHANGELOG.md +++ b/contracts/CHANGELOG.md @@ -1,5 +1,19 @@ +# Changelog + ## Unreleased +## [v1.1.4] (2023-02-21) + +- Problem 142 (rust-side) ([#3024]) + +## [nym-contracts-v1.1.3](https://github.com/nymtech/nym/tree/nym-contracts-v1.1.3) (2022-01-25) + +### Added + +- vesting-contract: `GetAccountsPaged` and `GetAccountsVestingCoinsPaged` queries for querying multiple accounts simultaneously ([#2791]) + +[#2791]: https://github.com/nymtech/nym/pull/2791 + ## [nym-contracts-v1.1.2](https://github.com/nymtech/nym/tree/nym-contracts-v1.1.2) (2022-12-07) ### Added diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock new file mode 100644 index 0000000000..b1c927514c --- /dev/null +++ b/contracts/Cargo.lock @@ -0,0 +1,2037 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "ctr", + "opaque-debug 0.3.0", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.8", + "once_cell", + "version_check", +] + +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[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 = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +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 = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "coconut-test" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-controllers", + "cw-multi-test", + "cw-storage-plus", + "cw-utils", + "cw3", + "cw3-flex-multisig", + "cw4", + "cw4-group", + "nym-bandwidth-claim-contract", + "nym-coconut-bandwidth", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg", + "nym-coconut-dkg-common", + "nym-group-contract-common", + "nym-multisig-contract-common", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "cosmwasm-crypto" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" +dependencies = [ + "digest 0.9.0", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" +dependencies = [ + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" +dependencies = [ + "schemars", + "serde_json", +] + +[[package]] +name = "cosmwasm-std" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" +dependencies = [ + "base64 0.13.1", + "cosmwasm-crypto", + "cosmwasm-derive", + "forward_ref", + "schemars", + "serde", + "serde-json-wasm", + "thiserror", + "uint", +] + +[[package]] +name = "cosmwasm-storage" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[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.6", + "rand_core 0.6.4", + "subtle 2.4.1", + "zeroize", +] + +[[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.6", + "subtle 2.4.1", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "cw-controllers" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-multi-test" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "cw-utils", + "derivative", + "itertools", + "prost", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + +[[package]] +name = "cw3" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw3-fixed-multisig" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54aa54c13f405ec4ab36b6217538bc957d439eee58f89312db05a79caf6706" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "cw2", + "cw3", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw3-flex-multisig" +version = "0.13.1" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus", + "cw-utils", + "cw2", + "cw3", + "cw3-fixed-multisig", + "cw4", + "cw4-group", + "nym-group-contract-common", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "cw4" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + +[[package]] +name = "cw4-group" +version = "0.13.4" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers", + "cw-storage-plus", + "cw-utils", + "cw2", + "cw4", + "nym-group-contract-common", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + +[[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 = "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.6", +] + +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand", + "serde", + "sha2", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek", + "hashbrown", + "hex", + "rand_core 0.6.4", + "serde", + "sha2", + "zeroize", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array 0.14.6", + "group", + "rand_core 0.6.4", + "sec1", + "subtle 2.4.1", + "zeroize", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355f93763ef7b0ae1c43c4d8eccc9d5848d84ad1a1d8ce61c421d1ac85a19d05" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[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 = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +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 = "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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +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]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[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 = "git2" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[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", +] + +[[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 = "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", + "serde", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sec1", + "sha2", +] + +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[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 = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "nym-bandwidth-claim-contract" +version = "0.1.0" +dependencies = [ + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-bandwidth" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-controllers", + "cw-storage-plus", + "nym-bandwidth-claim-contract", + "nym-coconut-bandwidth-contract-common", + "nym-multisig-contract-common", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-coconut-bandwidth-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-dkg" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-controllers", + "cw-multi-test", + "cw-storage-plus", + "cw4", + "cw4-group", + "lazy_static", + "nym-coconut-dkg-common", + "nym-group-contract-common", + "rusty-fork", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-coconut-dkg-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-contracts-common" +version = "0.3.0" +dependencies = [ + "bs58", + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-crypto" +version = "0.2.0" +dependencies = [ + "bs58", + "ed25519-dalek", + "nym-pemstore", + "nym-sphinx-types", + "rand", + "subtle-encoding", + "thiserror", + "x25519-dalek", +] + +[[package]] +name = "nym-group-contract-common" +version = "0.1.0" +dependencies = [ + "cw4", + "schemars", + "serde", +] + +[[package]] +name = "nym-mixnet-contract" +version = "1.2.0" +dependencies = [ + "bs58", + "cosmwasm-derive", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "cw2", + "nym-crypto", + "nym-mixnet-contract-common", + "nym-vesting-contract-common", + "rand_chacha 0.2.2", + "schemars", + "semver", + "serde", + "thiserror", + "time", + "vergen", +] + +[[package]] +name = "nym-mixnet-contract-common" +version = "0.3.0" +dependencies = [ + "bs58", + "cosmwasm-std", + "humantime-serde", + "log", + "nym-contracts-common", + "schemars", + "serde", + "serde_json", + "serde_repr", + "thiserror", + "time", +] + +[[package]] +name = "nym-multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw3-fixed-multisig", + "cw4", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-pemstore" +version = "0.2.0" +dependencies = [ + "pem", +] + +[[package]] +name = "nym-sphinx-types" +version = "0.2.0" +dependencies = [ + "sphinx-packet", +] + +[[package]] +name = "nym-vesting-contract" +version = "1.2.0" +dependencies = [ + "base64 0.21.0", + "cosmwasm-crypto", + "cosmwasm-derive", + "cosmwasm-std", + "cw-storage-plus", + "cw2", + "hex", + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-vesting-contract-common", + "rand_chacha 0.3.1", + "schemars", + "semver", + "serde", + "serde_json", + "thiserror", + "vergen", +] + +[[package]] +name = "nym-vesting-contract-common" +version = "0.3.0" +dependencies = [ + "cosmwasm-std", + "nym-contracts-common", + "nym-mixnet-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[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 = "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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der", + "spki", + "zeroize", +] + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.8", +] + +[[package]] +name = "rand_distr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.36.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "schemars" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array 0.14.6", + "pkcs8", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.3", +] + +[[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.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "signature" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.4", +] + +[[package]] +name = "sphinx-packet" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" +dependencies = [ + "aes", + "arrayref", + "blake2", + "bs58", + "byteorder", + "chacha", + "curve25519-dalek", + "digest 0.9.0", + "hkdf", + "hmac", + "lioness", + "log", + "rand", + "rand_distr", + "sha2", + "subtle 2.4.1", +] + +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[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 = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8234ae35e70582bfa0f1fedffa6daa248e41dd045310b19800c4a36382c8f60" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[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 = "tempfile" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.42.0", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.3", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d502c968c6a838ead8e69b2ee18ec708802f99db92a0d156705ec9ef801993b" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[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_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "x25519-dalek" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077" +dependencies = [ + "curve25519-dalek", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index de21d0fc92..553b276573 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -1,5 +1,21 @@ [workspace] -members = ["coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group", "coconut-test", "coconut-dkg"] +members = [ + "coconut-bandwidth", + "mixnet", + "vesting", + "multisig/cw3-flex-multisig", + "multisig/cw4-group", + "coconut-test", + "coconut-dkg" +] + +[workspace.package] +authors = ["Nym Technologies SA"] +repository = "https://github.com/nymtech/nym" +homepage = "https://nymtech.net" +documentation = "https://nymtech.net" +edition = "2021" +license = "Apache-2.0" [profile.release] opt-level = 3 @@ -10,4 +26,19 @@ debug-assertions = false codegen-units = 1 panic = 'abort' incremental = false -overflow-checks = true \ No newline at end of file +overflow-checks = true + +[workspace.dependencies] +cosmwasm-crypto = "=1.0.0" +cosmwasm-derive = "=1.0.0" +cosmwasm-schema = "=1.0.0" +cosmwasm-std = "=1.0.0" +cosmwasm-storage = "=1.0.0" +cw-controllers = "=0.13.4" +cw-multi-test = "=0.13.4" +cw-storage-plus = "=0.13.4" +cw-utils = "=0.13.4" +cw2 = "=0.13.4" +cw3 = "=0.13.4" +cw3-fixed-multisig = "=0.13.4" +cw4 = "=0.13.4" diff --git a/contracts/basic-bandwidth-generation/package-lock.json b/contracts/basic-bandwidth-generation/package-lock.json index c954a7d488..1498ec38eb 100644 --- a/contracts/basic-bandwidth-generation/package-lock.json +++ b/contracts/basic-bandwidth-generation/package-lock.json @@ -15,12 +15,12 @@ }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.5", - "@nomiclabs/hardhat-etherscan": "^3.0.3", + "@nomiclabs/hardhat-etherscan": "^3.1.0", "@nomiclabs/hardhat-waffle": "^2.0.3", "chai": "^4.3.6", "ethereum-waffle": "^3.4.4", "ethers": "^5.6.1", - "hardhat": "^2.9.2", + "hardhat": "^2.11.2", "solidity-coverage": "^0.7.20" } }, @@ -397,48 +397,6 @@ "node": ">=10.0" } }, - "node_modules/@ethereumjs/block": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/block/-/block-3.6.2.tgz", - "integrity": "sha512-mOqYWwMlAZpYUEOEqt7EfMFuVL2eyLqWWIzcf4odn6QgXY8jBI2NhVuJncrMCKeMZrsJAe7/auaRRB6YcdH+Qw==", - "dependencies": { - "@ethereumjs/common": "^2.6.3", - "@ethereumjs/tx": "^3.5.1", - "ethereumjs-util": "^7.1.4", - "merkle-patricia-tree": "^4.2.4" - } - }, - "node_modules/@ethereumjs/blockchain": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/blockchain/-/blockchain-5.5.2.tgz", - "integrity": "sha512-Jz26iJmmsQtngerW6r5BDFaew/f2mObLrRZo3rskLOx1lmtMZ8+TX/vJexmivrnWgmAsTdNWhlKUYY4thPhPig==", - "dependencies": { - "@ethereumjs/block": "^3.6.2", - "@ethereumjs/common": "^2.6.3", - "@ethereumjs/ethash": "^1.1.0", - "debug": "^4.3.3", - "ethereumjs-util": "^7.1.4", - "level-mem": "^5.0.1", - "lru-cache": "^5.1.1", - "semaphore-async-await": "^1.5.1" - } - }, - "node_modules/@ethereumjs/blockchain/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@ethereumjs/common": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.3.tgz", @@ -448,18 +406,6 @@ "ethereumjs-util": "^7.1.4" } }, - "node_modules/@ethereumjs/ethash": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/ethash/-/ethash-1.1.0.tgz", - "integrity": "sha512-/U7UOKW6BzpA+Vt+kISAoeDie1vAvY4Zy2KF5JJb+So7+1yKmJeJEHOGSnQIj330e9Zyl3L5Nae6VZyh2TJnAA==", - "dependencies": { - "@ethereumjs/block": "^3.5.0", - "@types/levelup": "^4.3.0", - "buffer-xor": "^2.0.1", - "ethereumjs-util": "^7.1.1", - "miller-rabin": "^4.0.0" - } - }, "node_modules/@ethereumjs/tx": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.1.tgz", @@ -469,41 +415,6 @@ "ethereumjs-util": "^7.1.4" } }, - "node_modules/@ethereumjs/vm": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/vm/-/vm-5.8.0.tgz", - "integrity": "sha512-mn2G2SX79QY4ckVvZUfxlNUpzwT2AEIkvgJI8aHoQaNYEHhH8rmdVDIaVVgz6//PjK52BZsK23afz+WvSR0Qqw==", - "dependencies": { - "@ethereumjs/block": "^3.6.2", - "@ethereumjs/blockchain": "^5.5.2", - "@ethereumjs/common": "^2.6.3", - "@ethereumjs/tx": "^3.5.1", - "async-eventemitter": "^0.2.4", - "core-js-pure": "^3.0.1", - "debug": "^4.3.3", - "ethereumjs-util": "^7.1.4", - "functional-red-black-tree": "^1.0.1", - "mcl-wasm": "^0.7.1", - "merkle-patricia-tree": "^4.2.4", - "rustbn.js": "~0.2.0" - } - }, - "node_modules/@ethereumjs/vm/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@ethersproject/abi": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.6.0.tgz", @@ -1235,6 +1146,28 @@ "rlp": "^2.2.3" } }, + "node_modules/@noble/hashes": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", + "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@noble/secp256k1": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", + "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1270,6 +1203,348 @@ "node": ">= 8" } }, + "node_modules/@nomicfoundation/ethereumjs-block": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz", + "integrity": "sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA==", + "dependencies": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-blockchain": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz", + "integrity": "sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw==", + "dependencies": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-ethash": "^2.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "abstract-level": "^1.0.3", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "level": "^8.0.0", + "lru-cache": "^5.1.1", + "memory-level": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-common": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz", + "integrity": "sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA==", + "dependencies": { + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "crc-32": "^1.2.0" + } + }, + "node_modules/@nomicfoundation/ethereumjs-ethash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz", + "integrity": "sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew==", + "dependencies": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "abstract-level": "^1.0.3", + "bigint-crypto-utils": "^3.0.23", + "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-evm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz", + "integrity": "sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q==", + "dependencies": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@types/async-eventemitter": "^0.2.1", + "async-eventemitter": "^0.2.4", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-rlp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz", + "integrity": "sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw==", + "bin": { + "rlp": "bin/rlp" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-statemanager": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz", + "integrity": "sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ==", + "dependencies": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "functional-red-black-tree": "^1.0.1" + } + }, + "node_modules/@nomicfoundation/ethereumjs-trie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz", + "integrity": "sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A==", + "dependencies": { + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-tx": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz", + "integrity": "sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w==", + "dependencies": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-util": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz", + "integrity": "sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A==", + "dependencies": { + "@nomicfoundation/ethereumjs-rlp": "^4.0.0-beta.2", + "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-vm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz", + "integrity": "sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w==", + "dependencies": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-evm": "^1.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@types/async-eventemitter": "^0.2.1", + "async-eventemitter": "^0.2.4", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "functional-red-black-tree": "^1.0.1", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.0.3.tgz", + "integrity": "sha512-VFMiOQvsw7nx5bFmrmVp2Q9rhIjw2AFST4DYvWVVO9PMHPE23BY2+kyfrQ4J3xCMFC8fcBbGLt7l4q7m1SlTqg==", + "engines": { + "node": ">= 12" + }, + "optionalDependencies": { + "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.0.3", + "@nomicfoundation/solidity-analyzer-darwin-x64": "0.0.3", + "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.0.3" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.0.3.tgz", + "integrity": "sha512-W+bIiNiZmiy+MTYFZn3nwjyPUO6wfWJ0lnXx2zZrM8xExKObMrhCh50yy8pQING24mHfpPFCn89wEB/iG7vZDw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.0.3.tgz", + "integrity": "sha512-HuJd1K+2MgmFIYEpx46uzwEFjvzKAI765mmoMxy4K+Aqq1p+q7hHRlsFU2kx3NB8InwotkkIq3A5FLU1sI1WDw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-freebsd-x64": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.0.3.tgz", + "integrity": "sha512-2cR8JNy23jZaO/vZrsAnWCsO73asU7ylrHIe0fEsXbZYqBP9sMr+/+xP3CELDHJxUbzBY8zqGvQt1ULpyrG+Kw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.0.3.tgz", + "integrity": "sha512-Eyv50EfYbFthoOb0I1568p+eqHGLwEUhYGOxcRNywtlTE9nj+c+MT1LA53HnxD9GsboH4YtOOmJOulrjG7KtbA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.0.3.tgz", + "integrity": "sha512-V8grDqI+ivNrgwEt2HFdlwqV2/EQbYAdj3hbOvjrA8Qv+nq4h9jhQUxFpegYMDtpU8URJmNNlXgtfucSrAQwtQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.0.3.tgz", + "integrity": "sha512-uRfVDlxtwT1vIy7MAExWAkRD4r9M79zMG7S09mCrWUn58DbLs7UFl+dZXBX0/8FTGYWHhOT/1Etw1ZpAf5DTrg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.0.3.tgz", + "integrity": "sha512-8HPwYdLbhcPpSwsE0yiU/aZkXV43vlXT2ycH+XlOjWOnLfH8C41z0njK8DHRtEFnp4OVN6E7E5lHBBKDZXCliA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.0.3.tgz", + "integrity": "sha512-5WWcT6ZNvfCuxjlpZOY7tdvOqT1kIQYlDF9Q42wMpZ5aTm4PvjdCmFDDmmTvyXEBJ4WTVmY5dWNWaxy8h/E28g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.0.3.tgz", + "integrity": "sha512-P/LWGZwWkyjSwkzq6skvS2wRc3gabzAbk6Akqs1/Iiuggql2CqdLBkcYWL5Xfv3haynhL+2jlNkak+v2BTZI4A==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.0.3.tgz", + "integrity": "sha512-4AcTtLZG1s/S5mYAIr/sdzywdNwJpOcdStGF3QMBzEt+cGn3MchMaS9b1gyhb2KKM2c39SmPF5fUuWq1oBSQZQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nomiclabs/hardhat-ethers": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.5.tgz", @@ -1281,18 +1556,21 @@ } }, "node_modules/@nomiclabs/hardhat-etherscan": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.0.3.tgz", - "integrity": "sha512-OfNtUKc/ZwzivmZnnpwWREfaYncXteKHskn3yDnz+fPBZ6wfM4GR+d5RwjREzYFWE+o5iR9ruXhWw/8fejWM9g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.0.tgz", + "integrity": "sha512-JroYgfN1AlYFkQTQ3nRwFi4o8NtZF7K/qFR2dxDUgHbCtIagkUseca9L4E/D2ScUm4XT40+8PbCdqZi+XmHyQA==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.1.2", "@ethersproject/address": "^5.0.2", "cbor": "^5.0.2", + "chalk": "^2.4.2", "debug": "^4.1.1", "fs-extra": "^7.0.1", + "lodash": "^4.17.11", "semver": "^6.3.0", - "undici": "^4.14.1" + "table": "^6.8.0", + "undici": "^5.4.0" }, "peerDependencies": { "hardhat": "^2.0.4" @@ -2038,6 +2316,48 @@ "ms": "^2.1.1" } }, + "node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@scure/bip32": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.0.tgz", + "integrity": "sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.1.1", + "@noble/secp256k1": "~1.6.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/@scure/bip39": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz", + "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.1.1", + "@scure/base": "~1.1.0" + } + }, "node_modules/@sentry/core": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", @@ -2163,6 +2483,7 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.1.tgz", "integrity": "sha512-eLjj2L6AuQjBB6s/ibwCAc0DwrR5Ge+ys+wgWo+bviU7fV2nTMQhU63CGaDKXg9iTmMxwhkyoggdIR7ZGRfMgw==", + "dev": true, "dependencies": { "antlr4ts": "^0.5.0-alpha.4" } @@ -2563,10 +2884,10 @@ "typechain": "^3.0.0" } }, - "node_modules/@types/abstract-leveldown": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", - "integrity": "sha512-q5veSX6zjUy/DlDhR4Y4cU0k2Ar+DT2LUraP00T19WLmTO6Se1djepCCaqU6nQrwcJ5Hyo/CWqxTzrrFg8eqbQ==" + "node_modules/@types/async-eventemitter": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz", + "integrity": "sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg==" }, "node_modules/@types/bignumber.js": { "version": "5.0.0", @@ -2600,21 +2921,6 @@ "@types/node": "*" } }, - "node_modules/@types/level-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/level-errors/-/level-errors-3.0.0.tgz", - "integrity": "sha512-/lMtoq/Cf/2DVOm6zE6ORyOM+3ZVm/BvzEZVxUhf6bgh8ZHglXlBqxbxSlJeVp8FCbD3IVvk/VbsaNmDjrQvqQ==" - }, - "node_modules/@types/levelup": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@types/levelup/-/levelup-4.3.3.tgz", - "integrity": "sha512-K+OTIjJcZHVlZQN1HmU64VtrC0jC3dXWQozuEIR9zVvltIk90zaGPM2AgT+fIkChpzHhFE3YnvFLCbLtzAmexA==", - "dependencies": { - "@types/abstract-leveldown": "*", - "@types/level-errors": "*", - "@types/node": "*" - } - }, "node_modules/@types/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2744,28 +3050,53 @@ "node": ">=6.5" } }, - "node_modules/abstract-leveldown": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz", - "integrity": "sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==", + "node_modules/abstract-level": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz", + "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==", "dependencies": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" + "buffer": "^6.0.3", + "catering": "^2.1.0", + "is-buffer": "^2.0.5", + "level-supports": "^4.0.0", + "level-transcoder": "^1.0.1", + "module-error": "^1.0.1", + "queue-microtask": "^1.2.3" }, "engines": { - "node": ">=6" + "node": ">=12" + } + }, + "node_modules/abstract-level/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" @@ -2897,7 +3228,8 @@ "node_modules/antlr4ts": { "version": "0.5.0-alpha.4", "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", - "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==" + "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", + "dev": true }, "node_modules/anymatch": { "version": "3.1.2", @@ -2989,10 +3321,19 @@ "node": "*" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dependencies": { "lodash": "^4.17.14" } @@ -3105,6 +3446,25 @@ "node": "*" } }, + "node_modules/bigint-crypto-utils": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.1.6.tgz", + "integrity": "sha512-k5ljSLHx94jQTW3+18KEfxLJR8/XFBHqhfhEGF48qT8p/jL6EdiG7oNOiiIRGMFh2wEP8kaCXZbVd+5dYkngUg==", + "dependencies": { + "bigint-mod-arith": "^3.1.0" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/bigint-mod-arith": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bigint-mod-arith/-/bigint-mod-arith-3.1.1.tgz", + "integrity": "sha512-SzFqdncZKXq5uh3oLFZXmzaZEMDsA7ml9l53xKaVGO6/+y26xNwAaTQEg2R+D+d07YduLbKi0dni3YPsR51UDQ==", + "engines": { + "node": ">=10.4.0" + } + }, "node_modules/bignumber.js": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", @@ -3137,23 +3497,26 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "dependencies": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/body-parser/node_modules/debug": { @@ -3164,53 +3527,11 @@ "ms": "2.0.0" } }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/body-parser/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/body-parser/node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -3263,6 +3584,17 @@ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, + "node_modules/browser-level": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz", + "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", + "dependencies": { + "abstract-level": "^1.0.2", + "catering": "^2.1.1", + "module-error": "^1.0.2", + "run-parallel-limit": "^1.1.0" + } + }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -3393,14 +3725,6 @@ "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", "integrity": "sha1-YGSkD6dutDxyOrqe+PbhIW0QURo=" }, - "node_modules/buffer-xor": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-2.0.2.tgz", - "integrity": "sha512-eHslX0bin3GB+Lx2p7lEYRShRewuNZL3fUl4qlVJGGiwoPGftmt8JQgk2Y9Ji5/01TnVDo33E5b5O3vUB1HdqQ==", - "dependencies": { - "safe-buffer": "^5.1.1" - } - }, "node_modules/bufferutil": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", @@ -3414,9 +3738,9 @@ } }, "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "engines": { "node": ">= 0.8" } @@ -3497,6 +3821,14 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "node_modules/catering": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", + "engines": { + "node": ">=6" + } + }, "node_modules/cbor": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/cbor/-/cbor-5.2.0.tgz", @@ -3698,6 +4030,22 @@ "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==" }, + "node_modules/classic-level": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.2.0.tgz", + "integrity": "sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg==", + "hasInstallScript": true, + "dependencies": { + "abstract-level": "^1.0.2", + "catering": "^2.1.0", + "module-error": "^1.0.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -3822,21 +4170,16 @@ } }, "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dependencies": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" }, "engines": { "node": ">= 0.6" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, "node_modules/content-hash": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/content-hash/-/content-hash-2.5.2.tgz", @@ -3873,16 +4216,6 @@ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" }, - "node_modules/core-js-pure": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", - "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -4062,9 +4395,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { "ms": "2.1.2" }, @@ -4089,9 +4422,9 @@ } }, "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "engines": { "node": ">=0.10" } @@ -4129,33 +4462,6 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" }, - "node_modules/deferred-leveldown": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", - "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", - "dependencies": { - "abstract-leveldown": "~6.2.1", - "inherits": "^2.0.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deferred-leveldown/node_modules/abstract-leveldown": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", - "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "dependencies": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -4181,11 +4487,11 @@ "integrity": "sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs=" }, "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/des.js": { @@ -4198,9 +4504,13 @@ } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detect-indent": { "version": "5.0.0", @@ -4370,7 +4680,7 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -4394,25 +4704,11 @@ "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "engines": { "node": ">= 0.8" } }, - "node_modules/encoding-down": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", - "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", - "dependencies": { - "abstract-leveldown": "^6.2.1", - "inherits": "^2.0.3", - "level-codec": "^9.0.0", - "level-errors": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -4448,17 +4744,6 @@ "node": ">=6" } }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4571,7 +4856,7 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/escape-string-regexp": { "version": "1.0.5", @@ -4663,7 +4948,7 @@ "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { "node": ">= 0.6" } @@ -4976,37 +5261,38 @@ } }, "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "dependencies": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -5016,9 +5302,9 @@ } }, "node_modules/express/node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -5036,19 +5322,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "node_modules/express/node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, "node_modules/ext": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", @@ -5165,16 +5438,16 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -5192,7 +5465,7 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/find-config": { "version": "1.0.0", @@ -5320,7 +5593,7 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { "node": ">= 0.6" } @@ -5351,6 +5624,19 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -5359,7 +5645,7 @@ "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" }, "node_modules/ganache-core": { "version": "2.13.2", @@ -14725,14 +15011,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "engines": { - "node": ">=4.x" - } - }, "node_modules/handlebars": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", @@ -14776,19 +15054,24 @@ } }, "node_modules/hardhat": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.9.2.tgz", - "integrity": "sha512-elTcUK1EdFverWinybQ+DoJzsM6sgiHUYs0ZYNNXMfESty6ESHiFSwkfJsC88/q09vmIz6YVaMh73BYnYd+feQ==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.11.2.tgz", + "integrity": "sha512-BdsXC1CFJQDJKmAgCwpmGhFuVU6dcqlgMgT0Kg/xmFAFVugkpYu6NRmh4AaJ3Fah0/BR9DOR4XgQGIbg4eon/Q==", "dependencies": { - "@ethereumjs/block": "^3.6.0", - "@ethereumjs/blockchain": "^5.5.0", - "@ethereumjs/common": "^2.6.0", - "@ethereumjs/tx": "^3.4.0", - "@ethereumjs/vm": "^5.6.0", "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-evm": "^1.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-vm": "^6.0.0", + "@nomicfoundation/solidity-analyzer": "^0.0.3", "@sentry/node": "^5.18.1", - "@solidity-parser/parser": "^0.14.1", "@types/bn.js": "^5.1.0", "@types/lru-cache": "^5.1.0", "abort-controller": "^3.0.0", @@ -14801,31 +15084,28 @@ "debug": "^4.1.1", "enquirer": "^2.3.0", "env-paths": "^2.2.0", - "ethereum-cryptography": "^0.1.2", + "ethereum-cryptography": "^1.0.3", "ethereumjs-abi": "^0.6.8", - "ethereumjs-util": "^7.1.3", "find-up": "^2.1.0", "fp-ts": "1.19.3", "fs-extra": "^7.0.1", - "glob": "^7.1.3", + "glob": "7.2.0", "immutable": "^4.0.0-rc.12", "io-ts": "1.10.4", + "keccak": "^3.0.2", "lodash": "^4.17.11", - "merkle-patricia-tree": "^4.2.2", "mnemonist": "^0.38.0", - "mocha": "^9.2.0", + "mocha": "^10.0.0", "p-map": "^4.0.0", "qs": "^6.7.0", "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", - "slash": "^3.0.0", "solc": "0.7.3", "source-map-support": "^0.5.13", "stacktrace-parser": "^0.1.10", - "true-case-path": "^2.2.1", "tsort": "0.0.1", - "undici": "^4.14.1", + "undici": "^5.4.0", "uuid": "^8.3.2", "ws": "^7.4.6" }, @@ -14833,7 +15113,30 @@ "hardhat": "internal/cli/cli.js" }, "engines": { - "node": "^12.0.0 || ^14.0.0 || ^16.0.0" + "node": "^14.0.0 || ^16.0.0 || ^18.0.0" + }, + "peerDependencies": { + "ts-node": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/hardhat/node_modules/ethereum-cryptography": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz", + "integrity": "sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ==", + "dependencies": { + "@noble/hashes": "1.1.2", + "@noble/secp256k1": "1.6.3", + "@scure/bip32": "1.1.0", + "@scure/bip39": "1.1.0" } }, "node_modules/hardhat/node_modules/uuid": { @@ -15008,18 +15311,18 @@ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "node_modules/http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/http-https": { @@ -15103,11 +15406,6 @@ "node": ">= 4" } }, - "node_modules/immediate": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", - "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" - }, "node_modules/immutable": { "version": "4.0.0-rc.15", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.15.tgz", @@ -15244,6 +15542,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, "node_modules/is-callable": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", @@ -15535,7 +15855,8 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "node_modules/iso-url": { "version": "0.4.7", @@ -15703,110 +16024,63 @@ "node": ">=0.10.0" } }, - "node_modules/level-codec": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", - "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", + "node_modules/level": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", + "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", "dependencies": { - "buffer": "^5.6.0" + "browser-level": "^1.0.1", + "classic-level": "^1.2.0" }, "engines": { - "node": ">=6" - } - }, - "node_modules/level-concat-iterator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", - "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/level-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", - "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", - "dependencies": { - "errno": "~0.1.1" + "node": ">=12" }, - "engines": { - "node": ">=6" - } - }, - "node_modules/level-iterator-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", - "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.4.0", - "xtend": "^4.0.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/level-mem": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/level-mem/-/level-mem-5.0.1.tgz", - "integrity": "sha512-qd+qUJHXsGSFoHTziptAKXoLX87QjR7v2KMbqncDXPxQuCdsQlzmyX+gwrEHhlzn08vkf8TyipYyMmiC6Gobzg==", - "dependencies": { - "level-packager": "^5.0.3", - "memdown": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/level-packager": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", - "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", - "dependencies": { - "encoding-down": "^6.3.0", - "levelup": "^4.3.2" - }, - "engines": { - "node": ">=6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/level" } }, "node_modules/level-supports": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", + "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/level-transcoder": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", - "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", + "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", "dependencies": { - "xtend": "^4.0.2" + "buffer": "^6.0.3", + "module-error": "^1.0.1" }, "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/level-ws": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-2.0.0.tgz", - "integrity": "sha512-1iv7VXx0G9ec1isqQZ7y5LmoZo/ewAsyDHNA8EFDW5hqH2Kqovm33nSFkSdnLLAK+I5FlT+lo5Cw9itGe+CpQA==", + "node_modules/level-transcoder/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^3.1.0", - "xtend": "^4.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/levelup": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", - "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", - "dependencies": { - "deferred-leveldown": "~5.3.0", - "level-errors": "~2.0.0", - "level-iterator-stream": "~4.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - }, - "engines": { - "node": ">=6" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, "node_modules/levn": { @@ -15889,6 +16163,12 @@ "resolved": "https://registry.npmjs.org/lodash.sum/-/lodash.sum-4.0.2.tgz", "integrity": "sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s=" }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -16010,11 +16290,6 @@ "yallist": "^3.0.2" } }, - "node_modules/ltgt": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", - "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" - }, "node_modules/mcl-wasm": { "version": "0.7.9", "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz", @@ -16041,42 +16316,19 @@ "node": ">= 0.6" } }, - "node_modules/memdown": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/memdown/-/memdown-5.1.0.tgz", - "integrity": "sha512-B3J+UizMRAlEArDjWHTMmadet+UKwHd3UjMgGBkZcKAxAYVPS9o0Yeiha4qvz7iGiL2Sb3igUft6p7nbFWctpw==", + "node_modules/memory-level": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/memory-level/-/memory-level-1.0.0.tgz", + "integrity": "sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==", "dependencies": { - "abstract-leveldown": "~6.2.1", - "functional-red-black-tree": "~1.0.1", - "immediate": "~3.2.3", - "inherits": "~2.0.1", - "ltgt": "~2.2.0", - "safe-buffer": "~5.2.0" + "abstract-level": "^1.0.0", + "functional-red-black-tree": "^1.0.1", + "module-error": "^1.0.1" }, "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/memdown/node_modules/abstract-leveldown": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", - "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "dependencies": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/memdown/node_modules/immediate": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz", - "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=" - }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -16099,19 +16351,6 @@ "node": ">= 8" } }, - "node_modules/merkle-patricia-tree": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-4.2.4.tgz", - "integrity": "sha512-eHbf/BG6eGNsqqfbLED9rIqbsF4+sykEaBn6OLNs71tjclbMcMOk1tEPmJKcNcNCLkvbpY/lwyOlizWsqPNo8w==", - "dependencies": { - "@types/levelup": "^4.3.0", - "ethereumjs-util": "^7.1.4", - "level-mem": "^5.0.1", - "level-ws": "^2.0.0", - "readable-stream": "^3.6.0", - "semaphore-async-await": "^1.5.1" - } - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -16157,19 +16396,19 @@ } }, "node_modules/mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.49.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -16274,41 +16513,39 @@ } }, "node_modules/mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", "dependencies": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", - "debug": "4.3.3", + "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.2.0", - "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "4.2.1", + "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.1", + "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", + "workerpool": "6.2.1", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "bin": { "_mocha": "bin/_mocha", - "mocha": "bin/mocha" + "mocha": "bin/mocha.js" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14.0.0" }, "funding": { "type": "opencollective", @@ -16320,27 +16557,14 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "balanced-match": "^1.0.0" } }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -16401,11 +16625,11 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=10" @@ -16466,25 +16690,19 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/mocha/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/mock-fs": { "version": "4.14.0", "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz", "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" }, + "node_modules/module-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -16540,9 +16758,9 @@ "integrity": "sha1-DMj20OK2IrR5xA1JnEbWS3Vcb18=" }, "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -16550,10 +16768,15 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" + }, "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { "node": ">= 0.6" } @@ -16765,9 +16988,9 @@ } }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -17228,11 +17451,6 @@ "node": ">= 0.10" } }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" - }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -17278,9 +17496,9 @@ } }, "node_modules/qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dependencies": { "side-channel": "^1.0.4" }, @@ -17318,7 +17536,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -17360,12 +17577,12 @@ } }, "node_modules/raw-body": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", - "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.3", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -17519,9 +17736,9 @@ } }, "node_modules/request/node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "engines": { "node": ">=0.6" } @@ -17638,6 +17855,28 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/run-parallel-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/rustbn.js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", @@ -17773,14 +18012,6 @@ "node": ">=10.0.0" } }, - "node_modules/semaphore-async-await": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz", - "integrity": "sha1-hXvvXjZEYBykuVcLh+nfXKEpdPo=", - "engines": { - "node": ">=4.1" - } - }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -17790,23 +18021,23 @@ } }, "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" @@ -17823,12 +18054,12 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/sentence-case": { "version": "2.1.1", @@ -17848,14 +18079,14 @@ } }, "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" @@ -17887,9 +18118,9 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/sha.js": { "version": "2.4.11", @@ -18018,10 +18249,61 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "engines": { "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/snake-case": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", @@ -18270,11 +18552,11 @@ } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/strict-uri-encode": { @@ -18542,6 +18824,65 @@ "ultron": "~1.1.0" } }, + "node_modules/table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tar": { "version": "4.4.19", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", @@ -18638,9 +18979,9 @@ } }, "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "engines": { "node": ">=0.6" } @@ -18671,11 +19012,6 @@ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "dev": true }, - "node_modules/true-case-path": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", - "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==" - }, "node_modules/ts-essentials": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-1.0.4.tgz", @@ -18826,7 +19162,7 @@ "version": "4.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", - "dev": true, + "devOptional": true, "peer": true, "bin": { "tsc": "bin/tsc", @@ -18880,9 +19216,9 @@ "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" }, "node_modules/undici": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-4.16.0.tgz", - "integrity": "sha512-tkZSECUYi+/T1i4u+4+lwZmQgLXd4BLGlrc7KZPcLIW7Jpq99+Xpc30ONv7nS6F5UNOxp/HBZSSL9MafUrvJbw==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.10.0.tgz", + "integrity": "sha512-c8HsD3IbwmjjbLvoZuRI26TZic+TSEe8FPMLLOkN1AfYRhdjnKBU6yL+IwcSCbdZiX4e5t0lfMDLDCqj4Sq70g==", "engines": { "node": ">=12.18" } @@ -19562,9 +19898,9 @@ "dev": true }, "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==" }, "node_modules/wrap-ansi": { "version": "7.0.0", @@ -20114,42 +20450,6 @@ "postinstall-postinstall": "^2.1.0" } }, - "@ethereumjs/block": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/block/-/block-3.6.2.tgz", - "integrity": "sha512-mOqYWwMlAZpYUEOEqt7EfMFuVL2eyLqWWIzcf4odn6QgXY8jBI2NhVuJncrMCKeMZrsJAe7/auaRRB6YcdH+Qw==", - "requires": { - "@ethereumjs/common": "^2.6.3", - "@ethereumjs/tx": "^3.5.1", - "ethereumjs-util": "^7.1.4", - "merkle-patricia-tree": "^4.2.4" - } - }, - "@ethereumjs/blockchain": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/blockchain/-/blockchain-5.5.2.tgz", - "integrity": "sha512-Jz26iJmmsQtngerW6r5BDFaew/f2mObLrRZo3rskLOx1lmtMZ8+TX/vJexmivrnWgmAsTdNWhlKUYY4thPhPig==", - "requires": { - "@ethereumjs/block": "^3.6.2", - "@ethereumjs/common": "^2.6.3", - "@ethereumjs/ethash": "^1.1.0", - "debug": "^4.3.3", - "ethereumjs-util": "^7.1.4", - "level-mem": "^5.0.1", - "lru-cache": "^5.1.1", - "semaphore-async-await": "^1.5.1" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - } - } - }, "@ethereumjs/common": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.3.tgz", @@ -20159,18 +20459,6 @@ "ethereumjs-util": "^7.1.4" } }, - "@ethereumjs/ethash": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/ethash/-/ethash-1.1.0.tgz", - "integrity": "sha512-/U7UOKW6BzpA+Vt+kISAoeDie1vAvY4Zy2KF5JJb+So7+1yKmJeJEHOGSnQIj330e9Zyl3L5Nae6VZyh2TJnAA==", - "requires": { - "@ethereumjs/block": "^3.5.0", - "@types/levelup": "^4.3.0", - "buffer-xor": "^2.0.1", - "ethereumjs-util": "^7.1.1", - "miller-rabin": "^4.0.0" - } - }, "@ethereumjs/tx": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.1.tgz", @@ -20180,35 +20468,6 @@ "ethereumjs-util": "^7.1.4" } }, - "@ethereumjs/vm": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/vm/-/vm-5.8.0.tgz", - "integrity": "sha512-mn2G2SX79QY4ckVvZUfxlNUpzwT2AEIkvgJI8aHoQaNYEHhH8rmdVDIaVVgz6//PjK52BZsK23afz+WvSR0Qqw==", - "requires": { - "@ethereumjs/block": "^3.6.2", - "@ethereumjs/blockchain": "^5.5.2", - "@ethereumjs/common": "^2.6.3", - "@ethereumjs/tx": "^3.5.1", - "async-eventemitter": "^0.2.4", - "core-js-pure": "^3.0.1", - "debug": "^4.3.3", - "ethereumjs-util": "^7.1.4", - "functional-red-black-tree": "^1.0.1", - "mcl-wasm": "^0.7.1", - "merkle-patricia-tree": "^4.2.4", - "rustbn.js": "~0.2.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - } - } - }, "@ethersproject/abi": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.6.0.tgz", @@ -20629,6 +20888,16 @@ } } }, + "@noble/hashes": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", + "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==" + }, + "@noble/secp256k1": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", + "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -20655,6 +20924,225 @@ "fastq": "^1.6.0" } }, + "@nomicfoundation/ethereumjs-block": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz", + "integrity": "sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA==", + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3" + } + }, + "@nomicfoundation/ethereumjs-blockchain": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz", + "integrity": "sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw==", + "requires": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-ethash": "^2.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "abstract-level": "^1.0.3", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "level": "^8.0.0", + "lru-cache": "^5.1.1", + "memory-level": "^1.0.0" + } + }, + "@nomicfoundation/ethereumjs-common": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz", + "integrity": "sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA==", + "requires": { + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "crc-32": "^1.2.0" + } + }, + "@nomicfoundation/ethereumjs-ethash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz", + "integrity": "sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew==", + "requires": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "abstract-level": "^1.0.3", + "bigint-crypto-utils": "^3.0.23", + "ethereum-cryptography": "0.1.3" + } + }, + "@nomicfoundation/ethereumjs-evm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz", + "integrity": "sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q==", + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@types/async-eventemitter": "^0.2.1", + "async-eventemitter": "^0.2.4", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + } + }, + "@nomicfoundation/ethereumjs-rlp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz", + "integrity": "sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw==" + }, + "@nomicfoundation/ethereumjs-statemanager": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz", + "integrity": "sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ==", + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "functional-red-black-tree": "^1.0.1" + } + }, + "@nomicfoundation/ethereumjs-trie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz", + "integrity": "sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A==", + "requires": { + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3", + "readable-stream": "^3.6.0" + } + }, + "@nomicfoundation/ethereumjs-tx": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz", + "integrity": "sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w==", + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3" + } + }, + "@nomicfoundation/ethereumjs-util": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz", + "integrity": "sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A==", + "requires": { + "@nomicfoundation/ethereumjs-rlp": "^4.0.0-beta.2", + "ethereum-cryptography": "0.1.3" + } + }, + "@nomicfoundation/ethereumjs-vm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz", + "integrity": "sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w==", + "requires": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-evm": "^1.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@types/async-eventemitter": "^0.2.1", + "async-eventemitter": "^0.2.4", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "functional-red-black-tree": "^1.0.1", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + } + }, + "@nomicfoundation/solidity-analyzer": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.0.3.tgz", + "integrity": "sha512-VFMiOQvsw7nx5bFmrmVp2Q9rhIjw2AFST4DYvWVVO9PMHPE23BY2+kyfrQ4J3xCMFC8fcBbGLt7l4q7m1SlTqg==", + "requires": { + "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.0.3", + "@nomicfoundation/solidity-analyzer-darwin-x64": "0.0.3", + "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.0.3" + } + }, + "@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.0.3.tgz", + "integrity": "sha512-W+bIiNiZmiy+MTYFZn3nwjyPUO6wfWJ0lnXx2zZrM8xExKObMrhCh50yy8pQING24mHfpPFCn89wEB/iG7vZDw==", + "optional": true + }, + "@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.0.3.tgz", + "integrity": "sha512-HuJd1K+2MgmFIYEpx46uzwEFjvzKAI765mmoMxy4K+Aqq1p+q7hHRlsFU2kx3NB8InwotkkIq3A5FLU1sI1WDw==", + "optional": true + }, + "@nomicfoundation/solidity-analyzer-freebsd-x64": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.0.3.tgz", + "integrity": "sha512-2cR8JNy23jZaO/vZrsAnWCsO73asU7ylrHIe0fEsXbZYqBP9sMr+/+xP3CELDHJxUbzBY8zqGvQt1ULpyrG+Kw==", + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.0.3.tgz", + "integrity": "sha512-Eyv50EfYbFthoOb0I1568p+eqHGLwEUhYGOxcRNywtlTE9nj+c+MT1LA53HnxD9GsboH4YtOOmJOulrjG7KtbA==", + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.0.3.tgz", + "integrity": "sha512-V8grDqI+ivNrgwEt2HFdlwqV2/EQbYAdj3hbOvjrA8Qv+nq4h9jhQUxFpegYMDtpU8URJmNNlXgtfucSrAQwtQ==", + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.0.3.tgz", + "integrity": "sha512-uRfVDlxtwT1vIy7MAExWAkRD4r9M79zMG7S09mCrWUn58DbLs7UFl+dZXBX0/8FTGYWHhOT/1Etw1ZpAf5DTrg==", + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.0.3.tgz", + "integrity": "sha512-8HPwYdLbhcPpSwsE0yiU/aZkXV43vlXT2ycH+XlOjWOnLfH8C41z0njK8DHRtEFnp4OVN6E7E5lHBBKDZXCliA==", + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.0.3.tgz", + "integrity": "sha512-5WWcT6ZNvfCuxjlpZOY7tdvOqT1kIQYlDF9Q42wMpZ5aTm4PvjdCmFDDmmTvyXEBJ4WTVmY5dWNWaxy8h/E28g==", + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.0.3.tgz", + "integrity": "sha512-P/LWGZwWkyjSwkzq6skvS2wRc3gabzAbk6Akqs1/Iiuggql2CqdLBkcYWL5Xfv3haynhL+2jlNkak+v2BTZI4A==", + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.0.3.tgz", + "integrity": "sha512-4AcTtLZG1s/S5mYAIr/sdzywdNwJpOcdStGF3QMBzEt+cGn3MchMaS9b1gyhb2KKM2c39SmPF5fUuWq1oBSQZQ==", + "optional": true + }, "@nomiclabs/hardhat-ethers": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.5.tgz", @@ -20663,18 +21151,21 @@ "requires": {} }, "@nomiclabs/hardhat-etherscan": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.0.3.tgz", - "integrity": "sha512-OfNtUKc/ZwzivmZnnpwWREfaYncXteKHskn3yDnz+fPBZ6wfM4GR+d5RwjREzYFWE+o5iR9ruXhWw/8fejWM9g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.0.tgz", + "integrity": "sha512-JroYgfN1AlYFkQTQ3nRwFi4o8NtZF7K/qFR2dxDUgHbCtIagkUseca9L4E/D2ScUm4XT40+8PbCdqZi+XmHyQA==", "dev": true, "requires": { "@ethersproject/abi": "^5.1.2", "@ethersproject/address": "^5.0.2", "cbor": "^5.0.2", + "chalk": "^2.4.2", "debug": "^4.1.1", "fs-extra": "^7.0.1", + "lodash": "^4.17.11", "semver": "^6.3.0", - "undici": "^4.14.1" + "table": "^6.8.0", + "undici": "^5.4.0" } }, "@nomiclabs/hardhat-truffle5": { @@ -21312,6 +21803,30 @@ } } }, + "@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==" + }, + "@scure/bip32": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.0.tgz", + "integrity": "sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q==", + "requires": { + "@noble/hashes": "~1.1.1", + "@noble/secp256k1": "~1.6.0", + "@scure/base": "~1.1.0" + } + }, + "@scure/bip39": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz", + "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", + "requires": { + "@noble/hashes": "~1.1.1", + "@scure/base": "~1.1.0" + } + }, "@sentry/core": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", @@ -21413,6 +21928,7 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.1.tgz", "integrity": "sha512-eLjj2L6AuQjBB6s/ibwCAc0DwrR5Ge+ys+wgWo+bviU7fV2nTMQhU63CGaDKXg9iTmMxwhkyoggdIR7ZGRfMgw==", + "dev": true, "requires": { "antlr4ts": "^0.5.0-alpha.4" } @@ -21801,10 +22317,10 @@ "ethers": "^5.0.2" } }, - "@types/abstract-leveldown": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", - "integrity": "sha512-q5veSX6zjUy/DlDhR4Y4cU0k2Ar+DT2LUraP00T19WLmTO6Se1djepCCaqU6nQrwcJ5Hyo/CWqxTzrrFg8eqbQ==" + "@types/async-eventemitter": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz", + "integrity": "sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg==" }, "@types/bignumber.js": { "version": "5.0.0", @@ -21837,21 +22353,6 @@ "@types/node": "*" } }, - "@types/level-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/level-errors/-/level-errors-3.0.0.tgz", - "integrity": "sha512-/lMtoq/Cf/2DVOm6zE6ORyOM+3ZVm/BvzEZVxUhf6bgh8ZHglXlBqxbxSlJeVp8FCbD3IVvk/VbsaNmDjrQvqQ==" - }, - "@types/levelup": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@types/levelup/-/levelup-4.3.3.tgz", - "integrity": "sha512-K+OTIjJcZHVlZQN1HmU64VtrC0jC3dXWQozuEIR9zVvltIk90zaGPM2AgT+fIkChpzHhFE3YnvFLCbLtzAmexA==", - "requires": { - "@types/abstract-leveldown": "*", - "@types/level-errors": "*", - "@types/node": "*" - } - }, "@types/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", @@ -21978,25 +22479,38 @@ "event-target-shim": "^5.0.0" } }, - "abstract-leveldown": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz", - "integrity": "sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==", + "abstract-level": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz", + "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==", "requires": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" + "buffer": "^6.0.3", + "catering": "^2.1.0", + "is-buffer": "^2.0.5", + "level-supports": "^4.0.0", + "level-transcoder": "^1.0.1", + "module-error": "^1.0.1", + "queue-microtask": "^1.2.3" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } } }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "address": { @@ -22091,7 +22605,8 @@ "antlr4ts": { "version": "0.5.0-alpha.4", "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", - "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==" + "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", + "dev": true }, "anymatch": { "version": "3.1.2", @@ -22165,10 +22680,16 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "requires": { "lodash": "^4.17.14" } @@ -22254,6 +22775,19 @@ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, + "bigint-crypto-utils": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.1.6.tgz", + "integrity": "sha512-k5ljSLHx94jQTW3+18KEfxLJR8/XFBHqhfhEGF48qT8p/jL6EdiG7oNOiiIRGMFh2wEP8kaCXZbVd+5dYkngUg==", + "requires": { + "bigint-mod-arith": "^3.1.0" + } + }, + "bigint-mod-arith": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bigint-mod-arith/-/bigint-mod-arith-3.1.1.tgz", + "integrity": "sha512-SzFqdncZKXq5uh3oLFZXmzaZEMDsA7ml9l53xKaVGO6/+y26xNwAaTQEg2R+D+d07YduLbKi0dni3YPsR51UDQ==" + }, "bignumber.js": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", @@ -22280,20 +22814,22 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "requires": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "debug": { @@ -22304,43 +22840,10 @@ "ms": "2.0.0" } }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } } } }, @@ -22392,6 +22895,17 @@ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, + "browser-level": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz", + "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", + "requires": { + "abstract-level": "^1.0.2", + "catering": "^2.1.1", + "module-error": "^1.0.2", + "run-parallel-limit": "^1.1.0" + } + }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -22514,14 +23028,6 @@ "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", "integrity": "sha1-YGSkD6dutDxyOrqe+PbhIW0QURo=" }, - "buffer-xor": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-2.0.2.tgz", - "integrity": "sha512-eHslX0bin3GB+Lx2p7lEYRShRewuNZL3fUl4qlVJGGiwoPGftmt8JQgk2Y9Ji5/01TnVDo33E5b5O3vUB1HdqQ==", - "requires": { - "safe-buffer": "^5.1.1" - } - }, "bufferutil": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", @@ -22531,9 +23037,9 @@ } }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "cacheable-request": { "version": "6.1.0", @@ -22592,6 +23098,11 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "catering": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==" + }, "cbor": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/cbor/-/cbor-5.2.0.tgz", @@ -22756,6 +23267,18 @@ "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==" }, + "classic-level": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.2.0.tgz", + "integrity": "sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg==", + "requires": { + "abstract-level": "^1.0.2", + "catering": "^2.1.0", + "module-error": "^1.0.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "^4.3.0" + } + }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -22861,18 +23384,11 @@ } }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } + "safe-buffer": "5.2.1" } }, "content-hash": { @@ -22905,11 +23421,6 @@ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" }, - "core-js-pure": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", - "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==" - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -23061,9 +23572,9 @@ "dev": true }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } @@ -23074,9 +23585,9 @@ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" }, "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==" }, "decompress-response": { "version": "3.3.0", @@ -23105,29 +23616,6 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" }, - "deferred-leveldown": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", - "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", - "requires": { - "abstract-leveldown": "~6.2.1", - "inherits": "^2.0.3" - }, - "dependencies": { - "abstract-leveldown": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", - "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "requires": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - } - } - } - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -23147,9 +23635,9 @@ "integrity": "sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs=" }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "des.js": { "version": "1.0.1", @@ -23161,9 +23649,9 @@ } }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "detect-indent": { "version": "5.0.0", @@ -23297,7 +23785,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "elliptic": { "version": "6.5.4", @@ -23321,18 +23809,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "encoding-down": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", - "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", - "requires": { - "abstract-leveldown": "^6.2.1", - "inherits": "^2.0.3", - "level-codec": "^9.0.0", - "level-errors": "^2.0.0" - } + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "end-of-stream": { "version": "1.4.4", @@ -23360,14 +23837,6 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "requires": { - "prr": "~1.0.1" - } - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -23461,7 +23930,7 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "escape-string-regexp": { "version": "1.0.5", @@ -23520,7 +23989,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "eth-ens-namehash": { "version": "2.0.8", @@ -23803,46 +24272,47 @@ "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "dependencies": { "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "debug": { "version": "2.6.9", @@ -23856,16 +24326,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -23971,16 +24431,16 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "dependencies": { @@ -23995,7 +24455,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -24089,7 +24549,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fs-extra": { "version": "7.0.1", @@ -24114,6 +24574,12 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -24122,7 +24588,7 @@ "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" }, "ganache-core": { "version": "2.13.2", @@ -31319,11 +31785,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" - }, "handlebars": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", @@ -31352,19 +31813,24 @@ } }, "hardhat": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.9.2.tgz", - "integrity": "sha512-elTcUK1EdFverWinybQ+DoJzsM6sgiHUYs0ZYNNXMfESty6ESHiFSwkfJsC88/q09vmIz6YVaMh73BYnYd+feQ==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.11.2.tgz", + "integrity": "sha512-BdsXC1CFJQDJKmAgCwpmGhFuVU6dcqlgMgT0Kg/xmFAFVugkpYu6NRmh4AaJ3Fah0/BR9DOR4XgQGIbg4eon/Q==", "requires": { - "@ethereumjs/block": "^3.6.0", - "@ethereumjs/blockchain": "^5.5.0", - "@ethereumjs/common": "^2.6.0", - "@ethereumjs/tx": "^3.4.0", - "@ethereumjs/vm": "^5.6.0", "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-evm": "^1.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-vm": "^6.0.0", + "@nomicfoundation/solidity-analyzer": "^0.0.3", "@sentry/node": "^5.18.1", - "@solidity-parser/parser": "^0.14.1", "@types/bn.js": "^5.1.0", "@types/lru-cache": "^5.1.0", "abort-controller": "^3.0.0", @@ -31377,35 +31843,43 @@ "debug": "^4.1.1", "enquirer": "^2.3.0", "env-paths": "^2.2.0", - "ethereum-cryptography": "^0.1.2", + "ethereum-cryptography": "^1.0.3", "ethereumjs-abi": "^0.6.8", - "ethereumjs-util": "^7.1.3", "find-up": "^2.1.0", "fp-ts": "1.19.3", "fs-extra": "^7.0.1", - "glob": "^7.1.3", + "glob": "7.2.0", "immutable": "^4.0.0-rc.12", "io-ts": "1.10.4", + "keccak": "^3.0.2", "lodash": "^4.17.11", - "merkle-patricia-tree": "^4.2.2", "mnemonist": "^0.38.0", - "mocha": "^9.2.0", + "mocha": "^10.0.0", "p-map": "^4.0.0", "qs": "^6.7.0", "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", - "slash": "^3.0.0", "solc": "0.7.3", "source-map-support": "^0.5.13", "stacktrace-parser": "^0.1.10", - "true-case-path": "^2.2.1", "tsort": "0.0.1", - "undici": "^4.14.1", + "undici": "^5.4.0", "uuid": "^8.3.2", "ws": "^7.4.6" }, "dependencies": { + "ethereum-cryptography": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz", + "integrity": "sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ==", + "requires": { + "@noble/hashes": "1.1.2", + "@noble/secp256k1": "1.6.3", + "@scure/bip32": "1.1.0", + "@scure/bip39": "1.1.0" + } + }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -31532,15 +32006,15 @@ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" } }, "http-https": { @@ -31594,11 +32068,6 @@ "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true }, - "immediate": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", - "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" - }, "immutable": { "version": "4.0.0-rc.15", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.15.tgz", @@ -31702,6 +32171,11 @@ "has-tostringtag": "^1.0.0" } }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + }, "is-callable": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", @@ -31890,7 +32364,8 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "iso-url": { "version": "0.4.7", @@ -32033,83 +32508,38 @@ "invert-kv": "^1.0.0" } }, - "level-codec": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", - "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", + "level": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", + "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", "requires": { - "buffer": "^5.6.0" - } - }, - "level-concat-iterator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", - "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==" - }, - "level-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", - "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", - "requires": { - "errno": "~0.1.1" - } - }, - "level-iterator-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", - "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.4.0", - "xtend": "^4.0.2" - } - }, - "level-mem": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/level-mem/-/level-mem-5.0.1.tgz", - "integrity": "sha512-qd+qUJHXsGSFoHTziptAKXoLX87QjR7v2KMbqncDXPxQuCdsQlzmyX+gwrEHhlzn08vkf8TyipYyMmiC6Gobzg==", - "requires": { - "level-packager": "^5.0.3", - "memdown": "^5.0.0" - } - }, - "level-packager": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", - "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", - "requires": { - "encoding-down": "^6.3.0", - "levelup": "^4.3.2" + "browser-level": "^1.0.1", + "classic-level": "^1.2.0" } }, "level-supports": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", + "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==" + }, + "level-transcoder": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", - "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", + "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", "requires": { - "xtend": "^4.0.2" - } - }, - "level-ws": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-2.0.0.tgz", - "integrity": "sha512-1iv7VXx0G9ec1isqQZ7y5LmoZo/ewAsyDHNA8EFDW5hqH2Kqovm33nSFkSdnLLAK+I5FlT+lo5Cw9itGe+CpQA==", - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^3.1.0", - "xtend": "^4.0.1" - } - }, - "levelup": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", - "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", - "requires": { - "deferred-leveldown": "~5.3.0", - "level-errors": "~2.0.0", - "level-iterator-stream": "~4.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" + "buffer": "^6.0.3", + "module-error": "^1.0.1" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } } }, "levn": { @@ -32183,6 +32613,12 @@ "resolved": "https://registry.npmjs.org/lodash.sum/-/lodash.sum-4.0.2.tgz", "integrity": "sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s=" }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -32276,11 +32712,6 @@ "yallist": "^3.0.2" } }, - "ltgt": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", - "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" - }, "mcl-wasm": { "version": "0.7.9", "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz", @@ -32301,36 +32732,14 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, - "memdown": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/memdown/-/memdown-5.1.0.tgz", - "integrity": "sha512-B3J+UizMRAlEArDjWHTMmadet+UKwHd3UjMgGBkZcKAxAYVPS9o0Yeiha4qvz7iGiL2Sb3igUft6p7nbFWctpw==", + "memory-level": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/memory-level/-/memory-level-1.0.0.tgz", + "integrity": "sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==", "requires": { - "abstract-leveldown": "~6.2.1", - "functional-red-black-tree": "~1.0.1", - "immediate": "~3.2.3", - "inherits": "~2.0.1", - "ltgt": "~2.2.0", - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "abstract-leveldown": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", - "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "requires": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - } - }, - "immediate": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz", - "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=" - } + "abstract-level": "^1.0.0", + "functional-red-black-tree": "^1.0.1", + "module-error": "^1.0.1" } }, "memorystream": { @@ -32349,19 +32758,6 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, - "merkle-patricia-tree": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-4.2.4.tgz", - "integrity": "sha512-eHbf/BG6eGNsqqfbLED9rIqbsF4+sykEaBn6OLNs71tjclbMcMOk1tEPmJKcNcNCLkvbpY/lwyOlizWsqPNo8w==", - "requires": { - "@types/levelup": "^4.3.0", - "ethereumjs-util": "^7.1.4", - "level-mem": "^5.0.1", - "level-ws": "^2.0.0", - "readable-stream": "^3.6.0", - "semaphore-async-await": "^1.5.1" - } - }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -32392,16 +32788,16 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.49.0" + "mime-db": "1.52.0" } }, "mimic-response": { @@ -32487,31 +32883,29 @@ } }, "mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", - "debug": "4.3.3", + "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.2.0", - "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "4.2.1", + "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.1", + "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", + "workerpool": "6.2.1", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -32522,19 +32916,12 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } + "balanced-match": "^1.0.0" } }, "escape-string-regexp": { @@ -32573,11 +32960,11 @@ } }, "minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" } }, "ms": { @@ -32613,14 +33000,6 @@ "requires": { "has-flag": "^4.0.0" } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -32629,6 +33008,11 @@ "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz", "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" }, + "module-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -32683,14 +33067,19 @@ "integrity": "sha1-DMj20OK2IrR5xA1JnEbWS3Vcb18=" }, "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==" + }, + "napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, "neo-async": { "version": "2.6.2", @@ -32850,9 +33239,9 @@ } }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -33191,11 +33580,6 @@ "ipaddr.js": "1.9.1" } }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -33234,9 +33618,9 @@ "integrity": "sha512-ksWccjmXOHU2gJBnH0cK1lSYdvSZ0zLoCMSz/nTGh6hDvCSgcRxDyIcOBD6KNxFz3xhMPm/T267Tbe2JRymKEQ==" }, "qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "requires": { "side-channel": "^1.0.4" } @@ -33260,8 +33644,7 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "randombytes": { "version": "2.1.0", @@ -33286,12 +33669,12 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", - "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.3", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -33413,9 +33796,9 @@ } }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" } } }, @@ -33495,6 +33878,14 @@ "queue-microtask": "^1.2.2" } }, + "run-parallel-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "requires": { + "queue-microtask": "^1.2.2" + } + }, "rustbn.js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", @@ -33595,34 +33986,29 @@ "node-gyp-build": "^4.2.0" } }, - "semaphore-async-await": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz", - "integrity": "sha1-hXvvXjZEYBykuVcLh+nfXKEpdPo=" - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -33636,14 +34022,14 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -33665,14 +34051,14 @@ } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" } }, "servify": { @@ -33698,9 +34084,9 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "sha.js": { "version": "2.4.11", @@ -33784,7 +34170,45 @@ "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } }, "snake-case": { "version": "2.1.0", @@ -33998,9 +34422,9 @@ } }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "strict-uri-encode": { "version": "1.1.0", @@ -34216,6 +34640,54 @@ } } }, + "table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "tar": { "version": "4.4.19", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", @@ -34292,9 +34764,9 @@ } }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "tough-cookie": { "version": "2.5.0", @@ -34318,11 +34790,6 @@ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "dev": true }, - "true-case-path": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", - "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==" - }, "ts-essentials": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-1.0.4.tgz", @@ -34449,7 +34916,7 @@ "version": "4.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", - "dev": true, + "devOptional": true, "peer": true }, "typical": { @@ -34487,9 +34954,9 @@ "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" }, "undici": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-4.16.0.tgz", - "integrity": "sha512-tkZSECUYi+/T1i4u+4+lwZmQgLXd4BLGlrc7KZPcLIW7Jpq99+Xpc30ONv7nS6F5UNOxp/HBZSSL9MafUrvJbw==" + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.10.0.tgz", + "integrity": "sha512-c8HsD3IbwmjjbLvoZuRI26TZic+TSEe8FPMLLOkN1AfYRhdjnKBU6yL+IwcSCbdZiX4e5t0lfMDLDCqj4Sq70g==" }, "universalify": { "version": "0.1.2", @@ -35056,9 +35523,9 @@ "dev": true }, "workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==" }, "wrap-ansi": { "version": "7.0.0", diff --git a/contracts/basic-bandwidth-generation/package.json b/contracts/basic-bandwidth-generation/package.json index 0fee523d6c..94ba8f7402 100644 --- a/contracts/basic-bandwidth-generation/package.json +++ b/contracts/basic-bandwidth-generation/package.json @@ -1,12 +1,12 @@ { "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.5", - "@nomiclabs/hardhat-etherscan": "^3.0.3", + "@nomiclabs/hardhat-etherscan": "^3.1.0", "@nomiclabs/hardhat-waffle": "^2.0.3", "chai": "^4.3.6", "ethereum-waffle": "^3.4.4", "ethers": "^5.6.1", - "hardhat": "^2.9.2", + "hardhat": "^2.11.2", "solidity-coverage": "^0.7.20" }, "dependencies": { diff --git a/contracts/coconut-bandwidth/Cargo.toml b/contracts/coconut-bandwidth/Cargo.toml index 3e3342bcd7..5064d41867 100644 --- a/contracts/coconut-bandwidth/Cargo.toml +++ b/contracts/coconut-bandwidth/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "coconut-bandwidth" +name = "nym-coconut-bandwidth" version = "0.1.0" edition = "2021" @@ -9,14 +9,14 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" } -coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } -multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } +nym-bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" } +nym-coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } +nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } -cosmwasm-std = "1.0.0" -cosmwasm-storage = "1.0.0" -cw-storage-plus = "0.13.4" -cw-controllers = "0.13.4" +cosmwasm-std = { workspace = true } +cosmwasm-storage = { workspace = true } +cw-storage-plus = { workspace = true } +cw-controllers = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/contracts/coconut-bandwidth/src/contract.rs b/contracts/coconut-bandwidth/src/contract.rs index a5cfa5a415..6ed901a443 100644 --- a/contracts/coconut-bandwidth/src/contract.rs +++ b/contracts/coconut-bandwidth/src/contract.rs @@ -5,7 +5,9 @@ use cosmwasm_std::{ entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, }; -use coconut_bandwidth_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use nym_coconut_bandwidth_contract_common::msg::{ + ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, +}; use crate::error::ContractError; use crate::queries::{query_all_spent_credentials_paged, query_spent_credential}; diff --git a/contracts/coconut-bandwidth/src/error.rs b/contracts/coconut-bandwidth/src/error.rs index 7bc8b11d4a..9f4aed6224 100644 --- a/contracts/coconut-bandwidth/src/error.rs +++ b/contracts/coconut-bandwidth/src/error.rs @@ -11,7 +11,7 @@ use thiserror::Error; /// Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("{0}")] + #[error(transparent)] Std(#[from] StdError), #[error("Received multiple coin types")] @@ -29,6 +29,6 @@ pub enum ContractError { #[error("Credential already spent or in process of spending")] DuplicateBlindedSerialNumber, - #[error("{0}")] + #[error(transparent)] Admin(#[from] AdminError), } diff --git a/contracts/coconut-bandwidth/src/queries.rs b/contracts/coconut-bandwidth/src/queries.rs index 1de10ef3a6..534696752f 100644 --- a/contracts/coconut-bandwidth/src/queries.rs +++ b/contracts/coconut-bandwidth/src/queries.rs @@ -1,11 +1,11 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_bandwidth_contract_common::spend_credential::{ - PagedSpendCredentialResponse, SpendCredential, SpendCredentialResponse, -}; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; +use nym_coconut_bandwidth_contract_common::spend_credential::{ + PagedSpendCredentialResponse, SpendCredential, SpendCredentialResponse, +}; use crate::storage::{self, SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT, SPEND_CREDENTIAL_PAGE_MAX_LIMIT}; diff --git a/contracts/coconut-bandwidth/src/storage.rs b/contracts/coconut-bandwidth/src/storage.rs index 549e64caaf..554c447672 100644 --- a/contracts/coconut-bandwidth/src/storage.rs +++ b/contracts/coconut-bandwidth/src/storage.rs @@ -1,8 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_bandwidth_contract_common::spend_credential::SpendCredential; use cw_storage_plus::{Index, IndexList, IndexedMap, UniqueIndex}; +use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredential; // storage prefixes const SPEND_CREDENTIAL_PK_NAMESPACE: &str = "sc"; @@ -44,10 +44,10 @@ mod tests { use crate::storage::SpendCredential; use crate::support::tests::fixtures; use crate::support::tests::fixtures::TEST_MIX_DENOM; - use coconut_bandwidth_contract_common::spend_credential::SpendCredentialStatus; use cosmwasm_std::testing::MockStorage; use cosmwasm_std::Addr; use cosmwasm_std::Coin; + use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialStatus; #[test] fn spend_credential_single_read_retrieval() { diff --git a/contracts/coconut-bandwidth/src/support/tests/fixtures.rs b/contracts/coconut-bandwidth/src/support/tests/fixtures.rs index c1c68a7f0c..1b5ac62dbe 100644 --- a/contracts/coconut-bandwidth/src/support/tests/fixtures.rs +++ b/contracts/coconut-bandwidth/src/support/tests/fixtures.rs @@ -1,8 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_bandwidth_contract_common::spend_credential::{SpendCredential, SpendCredentialData}; use cosmwasm_std::{Addr, Coin}; +use nym_coconut_bandwidth_contract_common::spend_credential::{ + SpendCredential, SpendCredentialData, +}; pub const TEST_MIX_DENOM: &str = "unym"; diff --git a/contracts/coconut-bandwidth/src/support/tests/helpers.rs b/contracts/coconut-bandwidth/src/support/tests/helpers.rs index 31a4a6f279..d5a4f275b6 100644 --- a/contracts/coconut-bandwidth/src/support/tests/helpers.rs +++ b/contracts/coconut-bandwidth/src/support/tests/helpers.rs @@ -5,9 +5,9 @@ pub const MULTISIG_CONTRACT: &str = "multisig contract address"; pub const POOL_CONTRACT: &str = "mix pool contract address"; use crate::contract::instantiate; -use coconut_bandwidth_contract_common::msg::InstantiateMsg; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps}; +use nym_coconut_bandwidth_contract_common::msg::InstantiateMsg; use super::fixtures::TEST_MIX_DENOM; diff --git a/contracts/coconut-bandwidth/src/transactions.rs b/contracts/coconut-bandwidth/src/transactions.rs index 92d3a28bd1..9d7baf40b3 100644 --- a/contracts/coconut-bandwidth/src/transactions.rs +++ b/contracts/coconut-bandwidth/src/transactions.rs @@ -1,17 +1,17 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_bandwidth_contract_common::spend_credential::{ +use cosmwasm_std::{BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Response}; +use nym_coconut_bandwidth_contract_common::spend_credential::{ to_cosmos_msg, SpendCredential, SpendCredentialData, }; -use cosmwasm_std::{BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Response}; use crate::error::ContractError; use crate::state::{ADMIN, CONFIG}; use crate::storage; -use coconut_bandwidth_contract_common::deposit::DepositData; -use coconut_bandwidth_contract_common::events::{ +use nym_coconut_bandwidth_contract_common::deposit::DepositData; +use nym_coconut_bandwidth_contract_common::events::{ DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO, DEPOSIT_VALUE, }; @@ -113,11 +113,11 @@ mod tests { use super::*; use crate::support::tests::fixtures::spend_credential_data_fixture; use crate::support::tests::helpers::{self, MULTISIG_CONTRACT, POOL_CONTRACT}; - use coconut_bandwidth_contract_common::msg::ExecuteMsg; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{from_binary, Coin, CosmosMsg, WasmMsg}; use cw_controllers::AdminError; - use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; + use nym_coconut_bandwidth_contract_common::msg::ExecuteMsg; + use nym_multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; #[test] fn invalid_deposit() { diff --git a/contracts/coconut-dkg/Cargo.toml b/contracts/coconut-dkg/Cargo.toml index fe4440cf2f..f877df138d 100644 --- a/contracts/coconut-dkg/Cargo.toml +++ b/contracts/coconut-dkg/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "coconut-dkg" +name = "nym-coconut-dkg" version = "0.1.0" edition = "2021" @@ -9,18 +9,21 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } +nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } -cosmwasm-std = "1.0.0" -cosmwasm-storage = "1.0.0" -cw-storage-plus = "0.13.4" -cw-controllers = "0.13.4" -cw4 = { version = "0.13.4" } +cosmwasm-std = { workspace = true } +cosmwasm-storage = { workspace = true } +cw-storage-plus = { workspace = true } +cw-controllers = { workspace = true } +cw4 = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = "1.0.23" [dev-dependencies] -cw-multi-test = { version = "0.13.4" } -cw4-group = { path = "../multisig/cw4-group" } \ No newline at end of file +cw-multi-test = { workspace = true } +cw4-group = { path = "../multisig/cw4-group" } +nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" } +lazy_static = "1.4" +rusty-fork = "0.3" diff --git a/contracts/coconut-dkg/src/constants.rs b/contracts/coconut-dkg/src/constants.rs index 249a96c696..ef7c79e13a 100644 --- a/contracts/coconut-dkg/src/constants.rs +++ b/contracts/coconut-dkg/src/constants.rs @@ -3,3 +3,6 @@ // Wait time for the verification to take place (currently 24h) pub(crate) const BLOCK_TIME_FOR_VERIFICATION_SECS: u64 = 86400; + +pub(crate) const VK_SHARES_PK_NAMESPACE: &str = "vksp"; +pub(crate) const VK_SHARES_EPOCH_ID_IDX_NAMESPACE: &str = "vkse"; diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index 5394e845af..9b3b13212a 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -7,20 +7,22 @@ use crate::dealers::queries::{ use crate::dealers::transactions::try_add_dealer; use crate::dealings::queries::query_dealings_paged; use crate::dealings::transactions::try_commit_dealings; -use crate::epoch_state::queries::{query_current_epoch_state, query_current_epoch_threshold}; -use crate::epoch_state::storage::CURRENT_EPOCH_STATE; -use crate::epoch_state::transactions::advance_epoch_state; +use crate::epoch_state::queries::{ + query_current_epoch, query_current_epoch_threshold, query_initial_dealers, +}; +use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::transactions::{advance_epoch_state, try_surpassed_threshold}; use crate::error::ContractError; -use crate::state::{State, ADMIN, MULTISIG, STATE}; +use crate::state::{State, MULTISIG, STATE}; use crate::verification_key_shares::queries::query_vk_shares_paged; use crate::verification_key_shares::transactions::try_commit_verification_key_share; use crate::verification_key_shares::transactions::try_verify_verification_key_share; -use coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use coconut_dkg_common::types::EpochState; use cosmwasm_std::{ entry_point, to_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, }; use cw4::Cw4Contract; +use nym_coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use nym_coconut_dkg_common::types::{Epoch, EpochState}; /// Instantiate the contract. /// @@ -30,13 +32,11 @@ use cw4::Cw4Contract; #[entry_point] pub fn instantiate( mut deps: DepsMut<'_>, - _env: Env, + env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> Result { - let admin_addr = deps.api.addr_validate(&msg.admin)?; let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?; - ADMIN.set(deps.branch(), Some(admin_addr))?; MULTISIG.set(deps.branch(), Some(multisig_addr.clone()))?; let group_addr = Cw4Contract(deps.api.addr_validate(&msg.group_addr).map_err(|_| { @@ -52,7 +52,15 @@ pub fn instantiate( }; STATE.save(deps.storage, &state)?; - CURRENT_EPOCH_STATE.save(deps.storage, &EpochState::default())?; + CURRENT_EPOCH.save( + deps.storage, + &Epoch::new( + EpochState::default(), + 0, + msg.time_configuration.unwrap_or_default(), + env.block.time, + ), + )?; Ok(Response::default()) } @@ -69,27 +77,31 @@ pub fn execute( ExecuteMsg::RegisterDealer { bte_key_with_proof, announce_address, - } => try_add_dealer(deps, info, bte_key_with_proof, announce_address), - ExecuteMsg::CommitDealing { dealing_bytes } => { - try_commit_dealings(deps, info, dealing_bytes) + resharing, + } => try_add_dealer(deps, info, bte_key_with_proof, announce_address, resharing), + ExecuteMsg::CommitDealing { + dealing_bytes, + resharing, + } => try_commit_dealings(deps, info, dealing_bytes, resharing), + ExecuteMsg::CommitVerificationKeyShare { share, resharing } => { + try_commit_verification_key_share(deps, env, info, share, resharing) } - ExecuteMsg::CommitVerificationKeyShare { share } => { - try_commit_verification_key_share(deps, env, info, share) + ExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => { + try_verify_verification_key_share(deps, info, owner, resharing) } - ExecuteMsg::VerifyVerificationKeyShare { owner } => { - try_verify_verification_key_share(deps, info, owner) - } - ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, info), + ExecuteMsg::SurpassedThreshold {} => try_surpassed_threshold(deps, env), + ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, env), } } #[entry_point] pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { let response = match msg { - QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch_state(deps.storage)?)?, + QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch(deps.storage)?)?, QueryMsg::GetCurrentEpochThreshold {} => { to_binary(&query_current_epoch_threshold(deps.storage)?)? } + QueryMsg::GetInitialDealers {} => to_binary(&query_initial_dealers(deps.storage)?)?, QueryMsg::GetDealerDetails { dealer_address } => { to_binary(&query_dealer_details(deps, dealer_address)?)? } @@ -104,9 +116,11 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result to_binary(&query_dealings_paged(deps, idx, start_after, limit)?)?, - QueryMsg::GetVerificationKeys { limit, start_after } => { - to_binary(&query_vk_shares_paged(deps, start_after, limit)?)? - } + QueryMsg::GetVerificationKeys { + epoch_id, + limit, + start_after, + } => to_binary(&query_vk_shares_paged(deps, epoch_id, start_after, limit)?)?, }; Ok(response) @@ -120,16 +134,15 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result Addr { let group_code_id = app.store_code(Box::new(ContractWrapper::new( @@ -163,7 +176,7 @@ mod tests { let msg = InstantiateMsg { group_addr: group_contract_addr.to_string(), multisig_addr: MULTISIG_CONTRACT.to_string(), - admin: Addr::unchecked(ADMIN_ADDRESS).to_string(), + time_configuration: None, mix_denom: TEST_MIX_DENOM.to_string(), }; app.instantiate_contract( @@ -198,7 +211,7 @@ mod tests { let msg = InstantiateMsg { group_addr: "group_addr".to_string(), multisig_addr: "multisig_addr".to_string(), - admin: "admin".to_string(), + time_configuration: None, mix_denom: "nym".to_string(), }; let info = mock_info("creator", &[]); @@ -230,6 +243,7 @@ mod tests { &RegisterDealer { bte_key_with_proof: "bte_key_with_proof".to_string(), announce_address: "127.0.0.1:8000".to_string(), + resharing: false, }, &vec![], ) @@ -243,6 +257,7 @@ mod tests { &RegisterDealer { bte_key_with_proof: "bte_key_with_proof".to_string(), announce_address: "127.0.0.1:8000".to_string(), + resharing: false, }, &vec![], ) @@ -258,6 +273,7 @@ mod tests { &RegisterDealer { bte_key_with_proof: "bte_key_with_proof".to_string(), announce_address: "127.0.0.1:8000".to_string(), + resharing: false, }, &vec![], ) diff --git a/contracts/coconut-dkg/src/dealers/queries.rs b/contracts/coconut-dkg/src/dealers/queries.rs index d4ed2ca04e..f7ef4209c1 100644 --- a/contracts/coconut-dkg/src/dealers/queries.rs +++ b/contracts/coconut-dkg/src/dealers/queries.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage::{self, IndexedDealersMap}; -use coconut_dkg_common::dealer::{DealerDetailsResponse, DealerType, PagedDealerResponse}; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; +use nym_coconut_dkg_common::dealer::{DealerDetailsResponse, DealerType, PagedDealerResponse}; fn query_dealers( deps: Deps<'_>, @@ -72,8 +72,7 @@ pub(crate) mod tests { use crate::dealers::storage::{DEALERS_PAGE_DEFAULT_LIMIT, DEALERS_PAGE_MAX_LIMIT}; use crate::support::tests::fixtures::dealer_details_fixture; use crate::support::tests::helpers::init_contract; - use cosmwasm_std::testing::mock_env; - use cosmwasm_std::{Addr, DepsMut}; + use cosmwasm_std::DepsMut; fn fill_dealers(deps: DepsMut<'_>, mapping: &IndexedDealersMap<'_>, size: usize) { for n in 0..size { @@ -95,8 +94,7 @@ pub(crate) mod tests { #[test] fn dealers_empty_on_init() { - let mut deps = init_contract(); - let env = mock_env(); + let deps = init_contract(); for mapping in [storage::current_dealers(), storage::past_dealers()] { let page1 = query_dealers(deps.as_ref(), None, None, &mapping).unwrap(); @@ -107,8 +105,6 @@ pub(crate) mod tests { #[test] fn dealers_paged_retrieval_obeys_limits() { let mut deps = init_contract(); - let env = mock_env(); - let owner = Addr::unchecked("owner"); let limit = 2; for mapping in [storage::current_dealers(), storage::past_dealers()] { @@ -124,7 +120,6 @@ pub(crate) mod tests { #[test] fn dealers_paged_retrieval_has_default_limit() { let mut deps = init_contract(); - let env = mock_env(); for mapping in [storage::current_dealers(), storage::past_dealers()] { fill_dealers(deps.as_mut(), &mapping, 1000); @@ -141,7 +136,6 @@ pub(crate) mod tests { #[test] fn dealers_paged_retrieval_has_max_limit() { let mut deps = init_contract(); - let env = mock_env(); // query with a crazily high limit in an attempt to use too many resources let crazy_limit = 1000 * DEALERS_PAGE_MAX_LIMIT; @@ -163,7 +157,6 @@ pub(crate) mod tests { #[test] fn dealers_pagination_works() { let mut deps = init_contract(); - let env = mock_env(); let per_page = 2; diff --git a/contracts/coconut-dkg/src/dealers/storage.rs b/contracts/coconut-dkg/src/dealers/storage.rs index 398193de23..b6c2c1f8bb 100644 --- a/contracts/coconut-dkg/src/dealers/storage.rs +++ b/contracts/coconut-dkg/src/dealers/storage.rs @@ -1,9 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::types::{DealerDetails, NodeIndex}; use cosmwasm_std::{Addr, StdResult, Storage}; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, UniqueIndex}; +use nym_coconut_dkg_common::types::{DealerDetails, NodeIndex}; const CURRENT_DEALERS_PK: &str = "crd"; const PAST_DEALERS_PK: &str = "ptd"; diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index 6c5ccbb8db..e7d019ee65 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -2,26 +2,33 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage as dealers_storage; +use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; -use crate::state::{State, STATE}; -use coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState}; +use crate::state::STATE; use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response}; +use nym_coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState}; // currently we only require that // a) it's part of the signer group // b) it isn't already a dealer -fn verify_dealer(deps: DepsMut<'_>, state: &State, dealer: &Addr) -> Result<(), ContractError> { +fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<(), ContractError> { if dealers_storage::current_dealers() .may_load(deps.storage, dealer)? .is_some() { return Err(ContractError::AlreadyADealer); } + let state = STATE.load(deps.storage)?; + let height = if resharing { + INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height + } else { + None + }; state .group_addr - .is_voting_member(&deps.querier, dealer, None)? + .is_voting_member(&deps.querier, dealer, height)? .ok_or(ContractError::Unauthorized {})?; Ok(()) @@ -32,11 +39,11 @@ pub fn try_add_dealer( info: MessageInfo, bte_key_with_proof: EncodedBTEPublicKeyWithProof, announce_address: String, + resharing: bool, ) -> Result { - check_epoch_state(deps.storage, EpochState::PublicKeySubmission)?; - let state = STATE.load(deps.storage)?; + check_epoch_state(deps.storage, EpochState::PublicKeySubmission { resharing })?; - verify_dealer(deps.branch(), &state, &info.sender)?; + verify_dealer(deps.branch(), &info.sender, resharing)?; // if it was already a dealer in the past, assign the same node index let node_index = if let Some(prior_details) = @@ -69,34 +76,93 @@ pub fn try_add_dealer( #[cfg(test)] pub(crate) mod tests { use super::*; + use crate::dealers::storage::current_dealers; use crate::epoch_state::transactions::advance_epoch_state; use crate::support::tests::fixtures::dealer_details_fixture; use crate::support::tests::helpers; - use crate::support::tests::helpers::ADMIN_ADDRESS; - use cosmwasm_std::testing::mock_info; + use crate::support::tests::helpers::{add_fixture_dealer, GROUP_MEMBERS}; + use cosmwasm_std::testing::{mock_env, mock_info}; + use cw4::Member; + use nym_coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration}; + use rusty_fork::rusty_fork_test; + + rusty_fork_test! { + #[test] + fn verification() { + let mut deps = helpers::init_contract(); + let new_dealer = Addr::unchecked("new_dealer"); + let details1 = dealer_details_fixture(1); + let details2 = dealer_details_fixture(2); + let details3 = dealer_details_fixture(3); + current_dealers() + .save(deps.as_mut().storage, &details1.address, &details1) + .unwrap(); + let err = verify_dealer(deps.as_mut(), &details1.address, false).unwrap_err(); + assert_eq!(err, ContractError::AlreadyADealer); + + INITIAL_REPLACEMENT_DATA + .save( + deps.as_mut().storage, + &InitialReplacementData { + initial_dealers: vec![details1.address, details2.address, details3.address], + initial_height: Some(1), + }, + ) + .unwrap(); + let err = verify_dealer(deps.as_mut(), &new_dealer, false).unwrap_err(); + assert_eq!(err, ContractError::Unauthorized); + + GROUP_MEMBERS.lock().unwrap().push(( + Member { + addr: new_dealer.to_string(), + weight: 10, + }, + 2, + )); + verify_dealer(deps.as_mut(), &new_dealer, false).unwrap(); + + let err = verify_dealer(deps.as_mut(), &new_dealer, true).unwrap_err(); + assert_eq!(err, ContractError::Unauthorized); + + INITIAL_REPLACEMENT_DATA + .update::<_, ContractError>(deps.as_mut().storage, |mut data| { + data.initial_height = Some(2); + Ok(data) + }) + .unwrap(); + verify_dealer(deps.as_mut(), &new_dealer, true).unwrap(); + } + } #[test] fn invalid_state() { let mut deps = helpers::init_contract(); let owner = Addr::unchecked("owner"); + let mut env = mock_env(); let info = mock_info(owner.as_str(), &[]); - let dealer_details = dealer_details_fixture(1); let bte_key_with_proof = String::from("bte_key_with_proof"); let announce_address = String::from("localhost:8000"); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); + + add_fixture_dealer(deps.as_mut()); + advance_epoch_state(deps.as_mut(), env).unwrap(); let ret = try_add_dealer( deps.as_mut(), info.clone(), bte_key_with_proof.clone(), announce_address.clone(), + false, ) .unwrap_err(); assert_eq!( ret, ContractError::IncorrectEpochState { - current_state: EpochState::DealingExchange.to_string(), + current_state: EpochState::DealingExchange { resharing: false }.to_string(), expected_state: EpochState::default().to_string(), } ); diff --git a/contracts/coconut-dkg/src/dealings/queries.rs b/contracts/coconut-dkg/src/dealings/queries.rs index 7fe476a458..a11b7bd4a5 100644 --- a/contracts/coconut-dkg/src/dealings/queries.rs +++ b/contracts/coconut-dkg/src/dealings/queries.rs @@ -3,10 +3,10 @@ use crate::dealings::storage; use crate::dealings::storage::DEALINGS_BYTES; -use coconut_dkg_common::dealer::{ContractDealing, PagedDealingsResponse}; -use coconut_dkg_common::types::TOTAL_DEALINGS; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; +use nym_coconut_dkg_common::dealer::{ContractDealing, PagedDealingsResponse}; +use nym_coconut_dkg_common::types::TOTAL_DEALINGS; pub fn query_dealings_paged( deps: Deps<'_>, @@ -50,7 +50,6 @@ pub(crate) mod tests { use crate::dealings::storage::{DEALINGS_PAGE_DEFAULT_LIMIT, DEALINGS_PAGE_MAX_LIMIT}; use crate::support::tests::fixtures::dealing_bytes_fixture; use crate::support::tests::helpers::init_contract; - use cosmwasm_std::testing::mock_env; use cosmwasm_std::{Addr, DepsMut}; fn fill_dealings(deps: DepsMut<'_>, size: usize) { @@ -68,7 +67,6 @@ pub(crate) mod tests { #[test] fn empty_on_bad_idx() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1000); for idx in TOTAL_DEALINGS as u64..100 * TOTAL_DEALINGS as u64 { @@ -89,7 +87,6 @@ pub(crate) mod tests { #[test] fn dealings_paged_retrieval_obeys_limits() { let mut deps = init_contract(); - let env = mock_env(); let limit = 2; fill_dealings(deps.as_mut(), 1000); @@ -103,7 +100,6 @@ pub(crate) mod tests { #[test] fn dealings_paged_retrieval_has_default_limit() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1000); for idx in 0..TOTAL_DEALINGS as u64 { @@ -117,7 +113,6 @@ pub(crate) mod tests { #[test] fn dealings_paged_retrieval_has_max_limit() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1000); // query with a crazily high limit in an attempt to use too many resources @@ -135,7 +130,6 @@ pub(crate) mod tests { #[test] fn dealings_pagination_works() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1); diff --git a/contracts/coconut-dkg/src/dealings/storage.rs b/contracts/coconut-dkg/src/dealings/storage.rs index 26cb5b35db..0395fa8c74 100644 --- a/contracts/coconut-dkg/src/dealings/storage.rs +++ b/contracts/coconut-dkg/src/dealings/storage.rs @@ -1,9 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::types::{ContractSafeBytes, TOTAL_DEALINGS}; use cosmwasm_std::Addr; use cw_storage_plus::Map; +use nym_coconut_dkg_common::types::{ContractSafeBytes, TOTAL_DEALINGS}; pub(crate) const DEALINGS_PAGE_MAX_LIMIT: u32 = 2; pub(crate) const DEALINGS_PAGE_DEFAULT_LIMIT: u32 = 1; diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index 7320768979..e5a9d9bb1b 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -3,17 +3,19 @@ use crate::dealers::storage as dealers_storage; use crate::dealings::storage::DEALINGS_BYTES; +use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; -use coconut_dkg_common::types::{ContractSafeBytes, EpochState}; use cosmwasm_std::{DepsMut, MessageInfo, Response}; +use nym_coconut_dkg_common::types::{ContractSafeBytes, EpochState}; pub fn try_commit_dealings( deps: DepsMut<'_>, info: MessageInfo, dealing_bytes: ContractSafeBytes, + resharing: bool, ) -> Result { - check_epoch_state(deps.storage, EpochState::DealingExchange)?; + check_epoch_state(deps.storage, EpochState::DealingExchange { resharing })?; // ensure the sender is a dealer if dealers_storage::current_dealers() .may_load(deps.storage, &info.sender)? @@ -21,6 +23,14 @@ pub fn try_commit_dealings( { return Err(ContractError::NotADealer); } + if resharing + && !INITIAL_REPLACEMENT_DATA + .load(deps.storage)? + .initial_dealers + .contains(&info.sender) + { + return Err(ContractError::NotAnInitialDealer); + } // check if this dealer has already committed to all dealings // (we don't want to allow overwriting anything) @@ -39,36 +49,43 @@ pub fn try_commit_dealings( #[cfg(test)] pub(crate) mod tests { use super::*; + use crate::epoch_state::storage::CURRENT_EPOCH; use crate::epoch_state::transactions::advance_epoch_state; - use crate::support::tests::fixtures::dealing_bytes_fixture; + use crate::support::tests::fixtures::{dealer_details_fixture, dealing_bytes_fixture}; use crate::support::tests::helpers; - use crate::support::tests::helpers::ADMIN_ADDRESS; - use coconut_dkg_common::dealer::DealerDetails; - use coconut_dkg_common::types::TOTAL_DEALINGS; - use cosmwasm_std::testing::mock_info; + use crate::support::tests::helpers::add_fixture_dealer; + use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::Addr; + use nym_coconut_dkg_common::dealer::DealerDetails; + use nym_coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration}; #[test] fn invalid_commit_dealing() { let mut deps = helpers::init_contract(); - let owner = Addr::unchecked("owner"); + let owner = Addr::unchecked("owner1"); + let mut env = mock_env(); let info = mock_info(owner.as_str(), &[]); let dealing_bytes = dealing_bytes_fixture(); - let ret = - try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err(); + let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false) + .unwrap_err(); assert_eq!( ret, ContractError::IncorrectEpochState { current_state: EpochState::default().to_string(), - expected_state: EpochState::DealingExchange.to_string() + expected_state: EpochState::DealingExchange { resharing: false }.to_string() } ); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); + add_fixture_dealer(deps.as_mut()); + advance_epoch_state(deps.as_mut(), env).unwrap(); - let ret = - try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err(); + let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false) + .unwrap_err(); assert_eq!(ret, ContractError::NotADealer); let dealer_details = DealerDetails { @@ -81,14 +98,41 @@ pub(crate) mod tests { .save(deps.as_mut().storage, &owner, &dealer_details) .unwrap(); + // assume we're in resharing mode + CURRENT_EPOCH + .update::<_, ContractError>(deps.as_mut().storage, |mut epoch| { + epoch.state = EpochState::DealingExchange { resharing: true }; + Ok(epoch) + }) + .unwrap(); + INITIAL_REPLACEMENT_DATA + .save( + deps.as_mut().storage, + &InitialReplacementData { + initial_dealers: vec![], + initial_height: None, + }, + ) + .unwrap(); + let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true) + .unwrap_err(); + assert_eq!(ret, ContractError::NotAnInitialDealer); + + INITIAL_REPLACEMENT_DATA + .update::<_, ContractError>(deps.as_mut().storage, |mut data| { + data.initial_dealers = vec![dealer_details_fixture(1).address]; + Ok(data) + }) + .unwrap(); + for dealings in DEALINGS_BYTES { assert!(!dealings.has(deps.as_mut().storage, &owner)); - let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()); + let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true); assert!(ret.is_ok()); assert!(dealings.has(deps.as_mut().storage, &owner)); } - let ret = - try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err(); + let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true) + .unwrap_err(); assert_eq!( ret, ContractError::AlreadyCommitted { diff --git a/contracts/coconut-dkg/src/epoch_state/queries.rs b/contracts/coconut-dkg/src/epoch_state/queries.rs index a7027ca488..a7b968bf3f 100644 --- a/contracts/coconut-dkg/src/epoch_state/queries.rs +++ b/contracts/coconut-dkg/src/epoch_state/queries.rs @@ -1,15 +1,13 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::{CURRENT_EPOCH_STATE, THRESHOLD}; +use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD}; use crate::error::ContractError; -use coconut_dkg_common::types::EpochState; use cosmwasm_std::Storage; +use nym_coconut_dkg_common::types::{Epoch, InitialReplacementData}; -pub(crate) fn query_current_epoch_state( - storage: &dyn Storage, -) -> Result { - CURRENT_EPOCH_STATE +pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result { + CURRENT_EPOCH .load(storage) .map_err(|_| ContractError::EpochNotInitialised) } @@ -20,16 +18,34 @@ pub(crate) fn query_current_epoch_threshold( Ok(THRESHOLD.may_load(storage)?) } +pub(crate) fn query_initial_dealers( + storage: &dyn Storage, +) -> Result, ContractError> { + Ok(INITIAL_REPLACEMENT_DATA.may_load(storage)?) +} + #[cfg(test)] pub(crate) mod test { use super::*; use crate::support::tests::helpers::init_contract; + use cosmwasm_std::testing::mock_env; + use nym_coconut_dkg_common::types::{EpochState, TimeConfiguration}; #[test] fn query_state() { let mut deps = init_contract(); - let state = query_current_epoch_state(deps.as_mut().storage).unwrap(); - assert_eq!(state, EpochState::PublicKeySubmission); + let epoch = query_current_epoch(deps.as_mut().storage).unwrap(); + assert_eq!( + epoch.state, + EpochState::PublicKeySubmission { resharing: false } + ); + assert_eq!( + epoch.finish_timestamp, + mock_env() + .block + .time + .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs) + ); } #[test] diff --git a/contracts/coconut-dkg/src/epoch_state/storage.rs b/contracts/coconut-dkg/src/epoch_state/storage.rs index b5c670d899..d146f5894a 100644 --- a/contracts/coconut-dkg/src/epoch_state/storage.rs +++ b/contracts/coconut-dkg/src/epoch_state/storage.rs @@ -1,8 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::types::EpochState; use cw_storage_plus::Item; +use nym_coconut_dkg_common::types::{Epoch, InitialReplacementData}; -pub(crate) const CURRENT_EPOCH_STATE: Item<'_, EpochState> = Item::new("current_epoch_state"); +pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch"); pub const THRESHOLD: Item = Item::new("threshold"); +pub const INITIAL_REPLACEMENT_DATA: Item = + Item::new("initial_replacement_data"); diff --git a/contracts/coconut-dkg/src/epoch_state/transactions.rs b/contracts/coconut-dkg/src/epoch_state/transactions.rs index fbb597f668..b2fb55125e 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions.rs @@ -1,97 +1,784 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::dealers::storage::current_dealers; -use crate::epoch_state::storage::{CURRENT_EPOCH_STATE, THRESHOLD}; +use crate::dealers::storage::{current_dealers, past_dealers}; +use crate::dealings::storage::DEALINGS_BYTES; +use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD}; +use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; -use crate::state::ADMIN; -use coconut_dkg_common::types::EpochState; -use cosmwasm_std::{DepsMut, MessageInfo, Order, Response}; +use crate::state::STATE; +use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage}; +use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData}; -pub(crate) fn advance_epoch_state( - deps: DepsMut<'_>, - info: MessageInfo, -) -> Result { - ADMIN.assert_admin(deps.as_ref(), &info.sender)?; - let current_epoch_state = - CURRENT_EPOCH_STATE.update::<_, ContractError>(deps.storage, |mut epoch_state| { - // TODO: When defaulting to the first state, some action will probably need to be taken on the - // rest of the contract, as we're starting with a new set of signers - epoch_state = epoch_state.next().unwrap_or_default(); - Ok(epoch_state) - })?; - if current_epoch_state == EpochState::DealingExchange { - let current_dealer_count = current_dealers() - .keys(deps.storage, None, None, Order::Ascending) - .count(); - // note: ceiling in integer division can be achieved via q = (x + y - 1) / y; - let threshold = (2 * current_dealer_count as u64 + 3 - 1) / 3; - THRESHOLD.save(deps.storage, &threshold)?; +fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> { + THRESHOLD.remove(storage); + let dealers: Vec<_> = current_dealers() + .keys(storage, None, None, Order::Ascending) + .collect::>()?; + + for dealer_addr in dealers { + let details = current_dealers().load(storage, &dealer_addr)?; + for dealings in DEALINGS_BYTES { + dealings.remove(storage, &details.address); + } + current_dealers().remove(storage, &dealer_addr)?; + past_dealers().save(storage, &dealer_addr, &details)?; } + Ok(()) +} + +fn dealers_still_active( + deps: &Deps<'_>, + dealers: impl Iterator, +) -> Result { + let state = STATE.load(deps.storage)?; + let mut still_active = 0; + for dealer_addr in dealers { + if state + .group_addr + .is_voting_member(&deps.querier, &dealer_addr, None)? + .is_some() + { + still_active += 1; + } + } + Ok(still_active) +} + +fn dealers_eq_members(deps: &DepsMut<'_>) -> Result { + let dealers_still_active = dealers_still_active( + &deps.as_ref(), + current_dealers() + .keys(deps.storage, None, None, Order::Ascending) + .flatten(), + )?; + let all_dealers = current_dealers() + .keys(deps.storage, None, None, Order::Ascending) + .count(); + let group_members = STATE + .load(deps.storage)? + .group_addr + .list_members(&deps.querier, None, None)? + .len(); + + Ok(dealers_still_active == all_dealers && all_dealers == group_members) +} + +fn replacement_threshold_surpassed(deps: &DepsMut<'_>) -> Result { + let threshold = THRESHOLD.load(deps.storage)? as usize; + let initial_dealers = INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_dealers; + let initial_dealer_count = initial_dealers.len(); + let replacement_threshold = threshold - (initial_dealers.len() + 2 - 1) / 2 + 1; + let removed_dealer_count = + initial_dealer_count - dealers_still_active(&deps.as_ref(), initial_dealers.into_iter())?; + + Ok(removed_dealer_count >= replacement_threshold) +} + +pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result { + let epoch = CURRENT_EPOCH.load(deps.storage)?; + if epoch.finish_timestamp > env.block.time { + return Err(ContractError::EarlyEpochStateAdvancement( + epoch + .finish_timestamp + .minus_seconds(env.block.time.seconds()) + .seconds(), + )); + } + + let current_epoch = CURRENT_EPOCH.load(deps.storage)?; + let next_epoch = if let Some(state) = current_epoch.state.next() { + // We are during DKG process + let mut new_state = state; + if let EpochState::DealingExchange { resharing } = state { + let current_dealers = current_dealers() + .keys(deps.storage, None, None, Order::Ascending) + .collect::, _>>()?; + if current_dealers.is_empty() { + // If no dealer registered yet, we just stay in the same state until there's at least one + new_state = current_epoch.state; + } else { + // note: ceiling in integer division can be achieved via q = (x + y - 1) / y; + let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3; + THRESHOLD.save(deps.storage, &threshold)?; + if !resharing { + let replacement_data = InitialReplacementData { + initial_dealers: current_dealers, + initial_height: None, + }; + INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?; + } + } + }; + Epoch::new( + new_state, + current_epoch.epoch_id, + current_epoch.time_configuration, + env.block.time, + ) + } else if dealers_eq_members(&deps)? { + // The dealer set hasn't changed, so we only extend the finish timestamp + // The epoch remains the same, as we use it as key for storing VKs + Epoch::new( + current_epoch.state, + current_epoch.epoch_id, + current_epoch.time_configuration, + env.block.time, + ) + } else { + // Dealer set changed, we need to redo DKG... + let state = if replacement_threshold_surpassed(&deps)? { + // ... in reset mode + EpochState::default() + } else { + // ... in reshare mode + INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| { + data.initial_height = Some(env.block.height); + Ok(data) + })?; + EpochState::PublicKeySubmission { resharing: true } + }; + reset_epoch_state(deps.storage)?; + Epoch::new( + state, + current_epoch.epoch_id + 1, + current_epoch.time_configuration, + env.block.time, + ) + }; + CURRENT_EPOCH.save(deps.storage, &next_epoch)?; + + Ok(Response::default()) +} + +pub(crate) fn try_surpassed_threshold( + deps: DepsMut<'_>, + env: Env, +) -> Result { + check_epoch_state(deps.storage, EpochState::InProgress)?; + + let threshold = THRESHOLD.load(deps.storage)?; + let dealers = current_dealers() + .keys(deps.storage, None, None, Order::Ascending) + .flatten(); + if dealers_still_active(&deps.as_ref(), dealers)? < threshold as usize { + reset_epoch_state(deps.storage)?; + CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| { + Ok(Epoch::new( + EpochState::default(), + epoch.epoch_id + 1, + epoch.time_configuration, + env.block.time, + )) + })?; + } + Ok(Response::default()) } #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS}; - use coconut_dkg_common::types::{DealerDetails, EpochState}; - use cosmwasm_std::testing::mock_info; + use crate::error::ContractError::EarlyEpochStateAdvancement; + use crate::support::tests::fixtures::dealer_details_fixture; + use crate::support::tests::helpers::{init_contract, GROUP_MEMBERS}; + use cosmwasm_std::testing::mock_env; use cosmwasm_std::Addr; - use cw_controllers::AdminError; + use cw4::Member; + use nym_coconut_dkg_common::types::{ + ContractSafeBytes, DealerDetails, EpochState, TimeConfiguration, + }; + use rusty_fork::rusty_fork_test; + + // Because of the global variable handling group, we need individual process for each test + + rusty_fork_test! { + // Using values from the DKG document + #[test] + fn threshold_surpassed() { + let mut deps = init_contract(); + let two_thirds = |n: u64| (2 * n + 3 - 1) / 3; + let three_fourths = |n: u64| (3 * n + 4 - 1) / 4; + let ninty_pc = |n: u64| (9 * n + 10 - 2) / 10; + let mut limits = [3, 4, 5, 5, 7, 11, 10, 14, 21, 18, 26, 41].iter(); + + for n in [10, 25, 50, 100] { + let dealers: Vec<_> = (0..n).map(dealer_details_fixture).collect(); + let initial_dealers = dealers.iter().map(|d| d.address.clone()).collect(); + let data = InitialReplacementData { + initial_dealers, + initial_height: None, + }; + for f in [two_thirds, three_fourths, ninty_pc] { + let threshold = f(n); + THRESHOLD.save(deps.as_mut().storage, &threshold).unwrap(); + INITIAL_REPLACEMENT_DATA + .save(deps.as_mut().storage, &data) + .unwrap(); + + let limit = *limits.next().unwrap(); + { + let mut group_members = GROUP_MEMBERS.lock().unwrap(); + for i in 0..n as usize { + group_members.push(( + Member { + addr: dealers[i].address.to_string(), + weight: 10, + }, + 1, + )); + } + for _ in 1..limit { + group_members.pop(); + } + } + assert!(!replacement_threshold_surpassed(&deps.as_mut()).unwrap()); + GROUP_MEMBERS.lock().unwrap().pop(); + assert!(replacement_threshold_surpassed(&deps.as_mut()).unwrap()); + + *GROUP_MEMBERS.lock().unwrap() = vec![]; + } + } + } + + #[test] + fn dealers_and_members() { + let mut deps = init_contract(); + + assert!(dealers_eq_members(&deps.as_mut()).unwrap()); + + let details = dealer_details_fixture(1); + let different_details = dealer_details_fixture(2); + current_dealers() + .save(deps.as_mut().storage, &details.address, &details) + .unwrap(); + assert!(!dealers_eq_members(&deps.as_mut()).unwrap()); + + current_dealers() + .remove(deps.as_mut().storage, &details.address) + .unwrap(); + GROUP_MEMBERS.lock().unwrap().push(( + Member { + addr: "owner1".to_string(), + weight: 10, + }, + 1, + )); + assert!(!dealers_eq_members(&deps.as_mut()).unwrap()); + + current_dealers() + .save( + deps.as_mut().storage, + &different_details.address, + &different_details, + ) + .unwrap(); + assert!(!dealers_eq_members(&deps.as_mut()).unwrap()); + + current_dealers() + .remove(deps.as_mut().storage, &different_details.address) + .unwrap(); + current_dealers() + .save(deps.as_mut().storage, &details.address, &details) + .unwrap(); + assert!(dealers_eq_members(&deps.as_mut()).unwrap()); + } + + #[test] + fn still_active() { + let mut deps = init_contract(); + { + let mut group = GROUP_MEMBERS.lock().unwrap(); + + group.push(( + Member { + addr: "owner1".to_string(), + weight: 10, + }, + 1, + )); + group.push(( + Member { + addr: "owner2".to_string(), + weight: 10, + }, + 1, + )); + group.push(( + Member { + addr: "owner3".to_string(), + weight: 10, + }, + 1, + )); + } + assert_eq!( + 0, + dealers_still_active( + &deps.as_ref(), + current_dealers() + .keys(&deps.storage, None, None, Order::Ascending) + .flatten() + ) + .unwrap() + ); + for i in 0..3 as u64 { + let details = dealer_details_fixture(i + 1); + current_dealers() + .save(deps.as_mut().storage, &details.address, &details) + .unwrap(); + assert_eq!( + i as usize + 1, + dealers_still_active( + &deps.as_ref(), + current_dealers() + .keys(&deps.storage, None, None, Order::Ascending) + .flatten() + ) + .unwrap() + ); + } + } + + #[test] + fn advance_state() { + let mut deps = init_contract(); + let mut env = mock_env(); + { + let mut group = GROUP_MEMBERS.lock().unwrap(); + + group.push(( + Member { + addr: "owner1".to_string(), + weight: 10, + }, + 1, + )); + group.push(( + Member { + addr: "owner2".to_string(), + weight: 10, + }, + 1, + )); + group.push(( + Member { + addr: "owner3".to_string(), + weight: 10, + }, + 1, + )); + group.push(( + Member { + addr: "owner4".to_string(), + weight: 10, + }, + 1, + )); + } + + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!( + epoch.state, + EpochState::PublicKeySubmission { resharing: false } + ); + assert_eq!( + epoch.finish_timestamp, + env.block + .time + .plus_seconds(epoch.time_configuration.public_key_submission_time_secs) + ); + + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.public_key_submission_time_secs - 1); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(1) + ); + + env.block.time = env.block.time.plus_seconds(1); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!( + epoch.state, + EpochState::PublicKeySubmission { resharing: false } + ); + + // setup dealer details + let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1)); + for details in all_details.iter() { + current_dealers() + .save(deps.as_mut().storage, &details.address, details) + .unwrap(); + } + + assert!(INITIAL_REPLACEMENT_DATA + .may_load(&deps.storage) + .unwrap() + .is_none()); + env.block.time = env.block.time.plus_seconds(epoch.time_configuration.public_key_submission_time_secs); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!( + epoch.state, + EpochState::DealingExchange { resharing: false } + ); + assert_eq!( + epoch.finish_timestamp, + env.block + .time + .plus_seconds(epoch.time_configuration.dealing_exchange_time_secs) + ); + let replacement_data = INITIAL_REPLACEMENT_DATA.load(&deps.storage).unwrap(); + let expected_replacement_data = InitialReplacementData { + initial_dealers: all_details.iter().map(|d| d.address.clone()).collect(), + initial_height: None, + }; + assert_eq!(replacement_data, expected_replacement_data); + + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.dealing_exchange_time_secs - 2); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(2) + ); + + env.block.time = env.block.time.plus_seconds(3); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!( + epoch.state, + EpochState::VerificationKeySubmission { resharing: false } + ); + assert_eq!( + epoch.finish_timestamp, + env.block.time.plus_seconds( + epoch + .time_configuration + .verification_key_submission_time_secs + ) + ); + + env.block.time = env.block.time.plus_seconds( + epoch + .time_configuration + .verification_key_submission_time_secs + - 2, + ); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(2) + ); + + env.block.time = env.block.time.plus_seconds(3); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!( + epoch.state, + EpochState::VerificationKeyValidation { resharing: false } + ); + assert_eq!( + epoch.finish_timestamp, + env.block.time.plus_seconds( + epoch + .time_configuration + .verification_key_validation_time_secs + ) + ); + + env.block.time = env.block.time.plus_seconds( + epoch + .time_configuration + .verification_key_validation_time_secs + - 3, + ); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(3) + ); + + env.block.time = env.block.time.plus_seconds(3); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!( + epoch.state, + EpochState::VerificationKeyFinalization { resharing: false } + ); + assert_eq!( + epoch.finish_timestamp, + env.block.time.plus_seconds( + epoch + .time_configuration + .verification_key_finalization_time_secs + ) + ); + + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().verification_key_finalization_time_secs - 1); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(1) + ); + + env.block.time = env.block.time.plus_seconds(1); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::InProgress); + assert_eq!( + epoch.finish_timestamp, + env.block + .time + .plus_seconds(epoch.time_configuration.in_progress_time_secs) + ); + + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.in_progress_time_secs - 100); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(100) + ); + + env.block.time = env.block.time.plus_seconds(50); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(50) + ); + + // Group hasn't changed, so we remain in the same epoch, with updated finish timestamp + env.block.time = env.block.time.plus_seconds(100); + let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let expected_epoch = Epoch::new( + EpochState::InProgress, + prev_epoch.epoch_id, + prev_epoch.time_configuration, + env.block.time, + ); + assert_eq!(curr_epoch, expected_epoch); + + // Group changed slightly, so re-run dkg in reshare mode + *GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = ( + Member { + addr: "owner5".to_string(), + weight: 10, + }, + 1, + ); + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.in_progress_time_secs); + let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let expected_epoch = Epoch::new( + EpochState::PublicKeySubmission { resharing: true }, + prev_epoch.epoch_id + 1, + prev_epoch.time_configuration, + env.block.time, + ); + assert_eq!(curr_epoch, expected_epoch); + assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none()); + + let all_details: [_; 2] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2)); + for details in all_details.iter() { + past_dealers().remove(deps.as_mut().storage, &details.address).unwrap(); + current_dealers() + .save(deps.as_mut().storage, &details.address, details) + .unwrap(); + } + for times in [ + epoch.time_configuration.public_key_submission_time_secs, + epoch.time_configuration.dealing_exchange_time_secs, + epoch.time_configuration.verification_key_submission_time_secs, + epoch.time_configuration.verification_key_validation_time_secs, + epoch.time_configuration.verification_key_finalization_time_secs, + ] { + env.block.time = env.block.time.plus_seconds(times); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + } + + // Group changed even more, surpassing threshold, so re-run dkg in reset mode + *GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = ( + Member { + addr: "owner6".to_string(), + weight: 10, + }, + 1, + ); + env.block.time = env + .block + .time + .plus_seconds(epoch.time_configuration.in_progress_time_secs); + let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let expected_epoch = Epoch::new( + EpochState::PublicKeySubmission { resharing: false }, + prev_epoch.epoch_id + 1, + prev_epoch.time_configuration, + env.block.time, + ); + assert_eq!(curr_epoch, expected_epoch); + assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none()); + } + + #[test] + fn surpass_threshold() { + let mut deps = init_contract(); + let mut env = mock_env(); + let time_configuration = TimeConfiguration::default(); + { + let mut group = GROUP_MEMBERS.lock().unwrap(); + + group.push(( + Member { + addr: "owner1".to_string(), + weight: 10, + }, + 1, + )); + group.push(( + Member { + addr: "owner2".to_string(), + weight: 10, + }, + 1, + )); + group.push(( + Member { + addr: "owner3".to_string(), + weight: 10, + }, + 1, + )); + } + + let ret = try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap_err(); + assert_eq!( + ret, + ContractError::IncorrectEpochState { + current_state: EpochState::default().to_string(), + expected_state: EpochState::InProgress.to_string() + } + ); + + let all_details: [_; 3] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1)); + for details in all_details.iter() { + current_dealers() + .save(deps.as_mut().storage, &details.address, details) + .unwrap(); + } + + for times in [ + time_configuration.public_key_submission_time_secs, + time_configuration.dealing_exchange_time_secs, + time_configuration.verification_key_submission_time_secs, + time_configuration.verification_key_validation_time_secs, + time_configuration.verification_key_finalization_time_secs, + ] { + env.block.time = env.block.time.plus_seconds(times); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + } + let curr_epoch = CURRENT_EPOCH.load(&deps.storage).unwrap(); + assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2); + + // epoch hasn't advanced as we are still in the threshold range + try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap(); + assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2); + assert_eq!(CURRENT_EPOCH.load(&deps.storage).unwrap(), curr_epoch); + + *GROUP_MEMBERS.lock().unwrap().first_mut().unwrap() = ( + Member { + addr: "owner4".to_string(), + weight: 10, + }, + 1, + ); + // epoch hasn't advanced as we are still in the threshold range + try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap(); + assert_eq!(THRESHOLD.load(&deps.storage).unwrap(), 2); + assert_eq!(CURRENT_EPOCH.load(&deps.storage).unwrap(), curr_epoch); + + *GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = ( + Member { + addr: "owner5".to_string(), + weight: 10, + }, + 1, + ); + try_surpassed_threshold(deps.as_mut(), env.clone()).unwrap(); + assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none()); + let next_epoch = CURRENT_EPOCH.load(&deps.storage).unwrap(); + assert_eq!( + next_epoch, + Epoch::new( + EpochState::default(), + curr_epoch.epoch_id + 1, + curr_epoch.time_configuration, + env.block.time, + ) + ); + } + } #[test] - fn advance_state() { + fn reset_state() { let mut deps = init_contract(); - let info = mock_info("requester", &[]); - let admin_info = mock_info(ADMIN_ADDRESS, &[]); + let all_details: [_; 100] = std::array::from_fn(|i| dealer_details_fixture(i as u64)); - assert_eq!( - advance_epoch_state(deps.as_mut(), info).unwrap_err(), - ContractError::Admin(AdminError::NotAdmin {}) - ); + THRESHOLD.save(deps.as_mut().storage, &42).unwrap(); + for details in all_details.iter() { + current_dealers() + .save(deps.as_mut().storage, &details.address, details) + .unwrap(); + for dealings in DEALINGS_BYTES { + dealings + .save( + deps.as_mut().storage, + &details.address, + &ContractSafeBytes(vec![1, 2, 3]), + ) + .unwrap(); + } + } - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); - assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::DealingExchange - ); + reset_epoch_state(deps.as_mut().storage).unwrap(); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); - assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::VerificationKeySubmission - ); - - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); - assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::VerificationKeyValidation - ); - - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); - assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::VerificationKeyFinalization - ); - - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); - assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::InProgress - ); - - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); - assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::PublicKeySubmission - ); + assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none()); + for details in all_details { + for dealings in DEALINGS_BYTES { + assert!(dealings + .may_load(&deps.storage, &details.address) + .unwrap() + .is_none()); + } + assert!(current_dealers() + .may_load(deps.as_mut().storage, &details.address) + .unwrap() + .is_none()); + assert_eq!( + past_dealers() + .load(&deps.storage, &details.address) + .unwrap(), + details + ); + } } #[test] fn verify_threshold() { let mut deps = init_contract(); - let admin_info = mock_info(ADMIN_ADDRESS, &[]); + let mut env = mock_env(); assert!(THRESHOLD.may_load(deps.as_mut().storage).unwrap().is_none()); @@ -111,7 +798,11 @@ pub(crate) mod tests { .unwrap(); } - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); + advance_epoch_state(deps.as_mut(), env).unwrap(); assert_eq!( THRESHOLD.may_load(deps.as_mut().storage).unwrap().unwrap(), 67 diff --git a/contracts/coconut-dkg/src/epoch_state/utils.rs b/contracts/coconut-dkg/src/epoch_state/utils.rs index b944ad361d..cfbe82dfba 100644 --- a/contracts/coconut-dkg/src/epoch_state/utils.rs +++ b/contracts/coconut-dkg/src/epoch_state/utils.rs @@ -1,16 +1,16 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::CURRENT_EPOCH_STATE; +use crate::epoch_state::storage::CURRENT_EPOCH; use crate::error::ContractError; -use coconut_dkg_common::types::EpochState; use cosmwasm_std::Storage; +use nym_coconut_dkg_common::types::EpochState; pub(crate) fn check_epoch_state( storage: &dyn Storage, against: EpochState, ) -> Result<(), ContractError> { - let epoch_state = CURRENT_EPOCH_STATE.load(storage)?; + let epoch_state = CURRENT_EPOCH.load(storage)?.state; if epoch_state != against { Err(ContractError::IncorrectEpochState { current_state: epoch_state.to_string(), @@ -25,14 +25,20 @@ pub(crate) fn check_epoch_state( pub(crate) mod test { use super::*; use crate::support::tests::helpers::init_contract; + use cosmwasm_std::testing::mock_env; + use nym_coconut_dkg_common::types::{Epoch, TimeConfiguration}; #[test] pub fn check_state() { let mut deps = init_contract(); + let env = mock_env(); for fixed_state in EpochState::default().all_until(EpochState::InProgress) { - CURRENT_EPOCH_STATE - .save(deps.as_mut().storage, &fixed_state) + CURRENT_EPOCH + .save( + deps.as_mut().storage, + &Epoch::new(fixed_state, 0, TimeConfiguration::default(), env.block.time), + ) .unwrap(); for against_state in EpochState::default().all_until(EpochState::InProgress) { let ret = check_epoch_state(deps.as_mut().storage, against_state); diff --git a/contracts/coconut-dkg/src/error.rs b/contracts/coconut-dkg/src/error.rs index 089a8b9f27..f8458d0c47 100644 --- a/contracts/coconut-dkg/src/error.rs +++ b/contracts/coconut-dkg/src/error.rs @@ -8,10 +8,10 @@ use thiserror::Error; /// Custom errors for contract failure conditions. #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("{0}")] + #[error(transparent)] Std(#[from] StdError), - #[error("{0}")] + #[error(transparent)] Admin(#[from] AdminError), #[error("Group contract invalid address '{addr}'")] @@ -23,6 +23,9 @@ pub enum ContractError { #[error("This sender is already a dealer for the epoch")] AlreadyADealer, + #[error("Too soon to advance epoch state. {0} more seconds until it can be advanced")] + EarlyEpochStateAdvancement(u64), + #[error("Epoch hasn't been correctly initialised!")] EpochNotInitialised, @@ -37,6 +40,9 @@ pub enum ContractError { #[error("This sender is not a dealer for the current epoch")] NotADealer, + #[error("This sender is not a dealer for the current resharing epoch")] + NotAnInitialDealer, + #[error("This dealer has already committed {commitment}")] AlreadyCommitted { commitment: String }, diff --git a/contracts/coconut-dkg/src/state/mod.rs b/contracts/coconut-dkg/src/state/mod.rs index 729618ec60..533bd98a0a 100644 --- a/contracts/coconut-dkg/src/state/mod.rs +++ b/contracts/coconut-dkg/src/state/mod.rs @@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize}; // unique items pub const STATE: Item = Item::new("state"); -pub const ADMIN: Admin = Admin::new("admin"); pub const MULTISIG: Admin = Admin::new("multisig"); #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] diff --git a/contracts/coconut-dkg/src/support/tests/fixtures.rs b/contracts/coconut-dkg/src/support/tests/fixtures.rs index e0bdee4e4d..1771ee1310 100644 --- a/contracts/coconut-dkg/src/support/tests/fixtures.rs +++ b/contracts/coconut-dkg/src/support/tests/fixtures.rs @@ -1,19 +1,20 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::dealer::{ContractDealing, DealerDetails}; -use coconut_dkg_common::types::ContractSafeBytes; -use coconut_dkg_common::verification_key::ContractVKShare; use cosmwasm_std::Addr; +use nym_coconut_dkg_common::dealer::DealerDetails; +use nym_coconut_dkg_common::types::ContractSafeBytes; +use nym_coconut_dkg_common::verification_key::ContractVKShare; pub const TEST_MIX_DENOM: &str = "unym"; -pub fn vk_share_fixture(index: u64) -> ContractVKShare { +pub fn vk_share_fixture(owner: &str, index: u64) -> ContractVKShare { ContractVKShare { share: format!("share{}", index), announce_address: format!("localhost:{}", index), node_index: index, - owner: Addr::unchecked(format!("owner{}", index)), + owner: Addr::unchecked(owner), + epoch_id: index, verified: index % 2 == 0, } } diff --git a/contracts/coconut-dkg/src/support/tests/helpers.rs b/contracts/coconut-dkg/src/support/tests/helpers.rs index ec46ff0215..f626b8bd89 100644 --- a/contracts/coconut-dkg/src/support/tests/helpers.rs +++ b/contracts/coconut-dkg/src/support/tests/helpers.rs @@ -2,9 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 use crate::contract::instantiate; -use coconut_dkg_common::msg::InstantiateMsg; +use crate::dealers::storage::current_dealers; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; -use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps}; +use cosmwasm_std::{ + from_binary, to_binary, Addr, ContractResult, DepsMut, Empty, MemoryStorage, OwnedDeps, + QuerierResult, SystemResult, WasmQuery, +}; +use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse}; +use lazy_static::lazy_static; +use nym_coconut_dkg_common::msg::InstantiateMsg; +use nym_coconut_dkg_common::types::DealerDetails; +use std::sync::Mutex; use super::fixtures::TEST_MIX_DENOM; @@ -12,12 +20,72 @@ pub const ADMIN_ADDRESS: &str = "admin address"; pub const GROUP_CONTRACT: &str = "group contract address"; pub const MULTISIG_CONTRACT: &str = "multisig contract address"; +lazy_static! { + pub static ref GROUP_MEMBERS: Mutex> = Mutex::new(vec![]); +} + +pub fn add_fixture_dealer(deps: DepsMut<'_>) { + let owner = Addr::unchecked("owner"); + current_dealers() + .save( + deps.storage, + &owner, + &DealerDetails { + address: owner.clone(), + bte_public_key_with_proof: String::new(), + announce_address: String::new(), + assigned_index: 100, + }, + ) + .unwrap(); +} + +fn querier_handler(query: &WasmQuery) -> QuerierResult { + let bin = match query { + WasmQuery::Smart { contract_addr, msg } => { + if contract_addr != GROUP_CONTRACT { + panic!("Not supported"); + } + match from_binary(msg) { + Ok(Cw4QueryMsg::Member { addr, at_height }) => { + let weight = GROUP_MEMBERS.lock().unwrap().iter().find_map(|(m, h)| { + if m.addr == addr { + if let Some(height) = at_height { + if height != *h { + return None; + } + } + Some(m.weight) + } else { + None + } + }); + to_binary(&MemberResponse { weight }).unwrap() + } + Ok(Cw4QueryMsg::ListMembers { .. }) => { + let members = GROUP_MEMBERS + .lock() + .unwrap() + .iter() + .map(|m| m.0.clone()) + .collect(); + to_binary(&MemberListResponse { members }).unwrap() + } + _ => panic!("Not supported"), + } + } + _ => panic!("Not supported"), + }; + SystemResult::Ok(ContractResult::Ok(bin)) +} + pub fn init_contract() -> OwnedDeps> { let mut deps = mock_dependencies(); + deps.querier.update_wasm(querier_handler); let msg = InstantiateMsg { group_addr: String::from(GROUP_CONTRACT), multisig_addr: String::from(MULTISIG_CONTRACT), - admin: String::from(ADMIN_ADDRESS), + time_configuration: None, mix_denom: TEST_MIX_DENOM.to_string(), }; let env = mock_env(); diff --git a/contracts/coconut-dkg/src/verification_key_shares/queries.rs b/contracts/coconut-dkg/src/verification_key_shares/queries.rs index e9c4d00dca..958fd6b68f 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/queries.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/queries.rs @@ -2,13 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use crate::verification_key_shares::storage; -use crate::verification_key_shares::storage::VK_SHARES; -use coconut_dkg_common::verification_key::PagedVKSharesResponse; +use crate::verification_key_shares::storage::vk_shares; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; +use nym_coconut_dkg_common::types::EpochId; +use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse; pub fn query_vk_shares_paged( deps: Deps<'_>, + epoch_id: EpochId, start_after: Option, limit: Option, ) -> StdResult { @@ -20,9 +22,12 @@ pub fn query_vk_shares_paged( .map(|addr| deps.api.addr_validate(&addr)) .transpose()?; - let start = addr.as_ref().map(Bound::exclusive); + let start = addr.as_ref().map(|addr| Bound::exclusive((addr, epoch_id))); - let shares = VK_SHARES + let shares = vk_shares() + .idx + .epoch_id + .prefix(epoch_id) .range(deps.storage, start, None, Order::Ascending) .take(limit) .map(|res| res.map(|(_, share)| share)) @@ -45,47 +50,65 @@ pub(crate) mod tests { use crate::verification_key_shares::storage::{ VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT, VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT, }; - use cosmwasm_std::testing::mock_env; use cosmwasm_std::Addr; + #[test] + fn separate_epoch_ids() { + let mut deps = init_contract(); + let vk_share1 = vk_share_fixture("owner", 1); + let vk_share2 = vk_share_fixture("owner", 2); + let sender = Addr::unchecked("owner"); + vk_shares() + .save(&mut deps.storage, (&sender, 2), &vk_share2) + .unwrap(); + + let response = query_vk_shares_paged(deps.as_ref(), 1, None, None).unwrap(); + assert_eq!(0, response.shares.len()); + vk_shares() + .save(&mut deps.storage, (&sender, 1), &vk_share1) + .unwrap(); + let response = query_vk_shares_paged(deps.as_ref(), 1, None, None).unwrap(); + assert_eq!(1, response.shares.len()); + } + #[test] fn vk_shares_empty_on_init() { let deps = init_contract(); - let response = query_vk_shares_paged(deps.as_ref(), None, Option::from(2)).unwrap(); + let response = query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(2)).unwrap(); assert_eq!(0, response.shares.len()); } #[test] fn vk_shares_paged_retrieval_obeys_limits() { let mut deps = init_contract(); - let env = mock_env(); let limit = 2; for n in 0..1000 { - let vk_share = vk_share_fixture(n); - let sender = Addr::unchecked(format!("owner{}", n)); - VK_SHARES - .save(&mut deps.storage, &sender, &vk_share) + let owner = format!("owner{}", n); + let vk_share = vk_share_fixture(&owner, 0); + let sender = Addr::unchecked(owner); + vk_shares() + .save(&mut deps.storage, (&sender, 0), &vk_share) .unwrap(); } - let page1 = query_vk_shares_paged(deps.as_ref(), None, Option::from(limit)).unwrap(); + let page1 = query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(limit)).unwrap(); assert_eq!(limit, page1.shares.len() as u32); } #[test] fn vk_shares_paged_retrieval_has_default_limit() { let mut deps = init_contract(); - let env = mock_env(); for n in 0..1000 { - let vk_share = vk_share_fixture(n); - let sender = Addr::unchecked(format!("owner{}", n)); - VK_SHARES - .save(&mut deps.storage, &sender, &vk_share) + let owner = format!("owner{}", n); + let vk_share = vk_share_fixture(&owner, 0); + let sender = Addr::unchecked(owner); + vk_shares() + .save(&mut deps.storage, (&sender, 0), &vk_share) .unwrap(); } // query without explicitly setting a limit - let page1 = query_vk_shares_paged(deps.as_ref(), None, None).unwrap(); + let page1 = query_vk_shares_paged(deps.as_ref(), 0, None, None).unwrap(); assert_eq!( VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT, @@ -96,18 +119,19 @@ pub(crate) mod tests { #[test] fn vk_shares_paged_retrieval_has_max_limit() { let mut deps = init_contract(); - let env = mock_env(); for n in 0..1000 { - let vk_share = vk_share_fixture(n); - let sender = Addr::unchecked(format!("owner{}", n)); - VK_SHARES - .save(&mut deps.storage, &sender, &vk_share) + let owner = format!("owner{}", n); + let vk_share = vk_share_fixture(&owner, 0); + let sender = Addr::unchecked(owner); + vk_shares() + .save(&mut deps.storage, (&sender, 0), &vk_share) .unwrap(); } // query with a crazily high limit in an attempt to use too many resources let crazy_limit = 1000 * VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT; - let page1 = query_vk_shares_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap(); + let page1 = + query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(crazy_limit)).unwrap(); // we default to a decent sized upper bound instead let expected_limit = VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT; @@ -117,45 +141,48 @@ pub(crate) mod tests { #[test] fn vk_shares_pagination_works() { let mut deps = init_contract(); - let env = mock_env(); - let vk_share = vk_share_fixture(1); - let sender = Addr::unchecked(format!("owner{}", 1)); - VK_SHARES - .save(&mut deps.storage, &sender, &vk_share) + let owner = format!("owner{}", 1); + let vk_share = vk_share_fixture(&owner, 0); + let sender = Addr::unchecked(owner); + vk_shares() + .save(&mut deps.storage, (&sender, 0), &vk_share) .unwrap(); let per_page = 2; - let page1 = query_vk_shares_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + let page1 = query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(per_page)).unwrap(); // page should have 1 result on it assert_eq!(1, page1.shares.len()); // save another - let vk_share = vk_share_fixture(2); - let sender = Addr::unchecked(format!("owner{}", 2)); - VK_SHARES - .save(&mut deps.storage, &sender, &vk_share) + let owner = format!("owner{}", 2); + let vk_share = vk_share_fixture(&owner, 0); + let sender = Addr::unchecked(owner); + vk_shares() + .save(&mut deps.storage, (&sender, 0), &vk_share) .unwrap(); // page1 should have 2 results on it - let page1 = query_vk_shares_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + let page1 = query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(per_page)).unwrap(); assert_eq!(2, page1.shares.len()); - let vk_share = vk_share_fixture(3); - let sender = Addr::unchecked(format!("owner{}", 3)); - VK_SHARES - .save(&mut deps.storage, &sender, &vk_share) + let owner = format!("owner{}", 3); + let vk_share = vk_share_fixture(&owner, 0); + let sender = Addr::unchecked(owner); + vk_shares() + .save(&mut deps.storage, (&sender, 0), &vk_share) .unwrap(); // page1 still has 2 results - let page1 = query_vk_shares_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + let page1 = query_vk_shares_paged(deps.as_ref(), 0, None, Option::from(per_page)).unwrap(); assert_eq!(2, page1.shares.len()); // retrieving the next page should start after the last key on this page let start_after = page1.start_next_after.unwrap(); let page2 = query_vk_shares_paged( deps.as_ref(), + 0, Option::from(start_after.to_string()), Option::from(per_page), ) @@ -163,14 +190,16 @@ pub(crate) mod tests { assert_eq!(1, page2.shares.len()); - let vk_share = vk_share_fixture(4); - let sender = Addr::unchecked(format!("owner{}", 4)); - VK_SHARES - .save(&mut deps.storage, &sender, &vk_share) + let owner = format!("owner{}", 4); + let vk_share = vk_share_fixture(&owner, 0); + let sender = Addr::unchecked(owner); + vk_shares() + .save(&mut deps.storage, (&sender, 0), &vk_share) .unwrap(); let page2 = query_vk_shares_paged( deps.as_ref(), + 0, Option::from(start_after.to_string()), Option::from(per_page), ) diff --git a/contracts/coconut-dkg/src/verification_key_shares/storage.rs b/contracts/coconut-dkg/src/verification_key_shares/storage.rs index 761736367c..b27605322d 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/storage.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/storage.rs @@ -3,13 +3,35 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::verification_key::ContractVKShare; +use crate::constants::{VK_SHARES_EPOCH_ID_IDX_NAMESPACE, VK_SHARES_PK_NAMESPACE}; use cosmwasm_std::Addr; -use cw_storage_plus::Map; +use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex}; +use nym_coconut_dkg_common::types::EpochId; +use nym_coconut_dkg_common::verification_key::ContractVKShare; pub(crate) const VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT: u32 = 75; pub(crate) const VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT: u32 = 50; -type VKShareKey<'a> = &'a Addr; +type VKShareKey<'a> = (&'a Addr, EpochId); -pub(crate) const VK_SHARES: Map<'_, VKShareKey<'_>, ContractVKShare> = Map::new("vks"); +pub(crate) struct VkShareIndex<'a> { + pub(crate) epoch_id: MultiIndex<'a, EpochId, ContractVKShare, VKShareKey<'a>>, +} + +impl<'a> IndexList for VkShareIndex<'a> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.epoch_id]; + Box::new(v.into_iter()) + } +} + +pub(crate) fn vk_shares<'a>() -> IndexedMap<'a, VKShareKey<'a>, ContractVKShare, VkShareIndex<'a>> { + let indexes = VkShareIndex { + epoch_id: MultiIndex::new( + |d| d.epoch_id, + VK_SHARES_PK_NAMESPACE, + VK_SHARES_EPOCH_ID_IDX_NAMESPACE, + ), + }; + IndexedMap::new(VK_SHARES_PK_NAMESPACE, indexes) +} diff --git a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs index 0f76fc7811..677e4d70d6 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs @@ -3,26 +3,37 @@ use crate::constants::BLOCK_TIME_FOR_VERIFICATION_SECS; use crate::dealers::storage as dealers_storage; +use crate::epoch_state::storage::CURRENT_EPOCH; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; use crate::state::{MULTISIG, STATE}; -use crate::verification_key_shares::storage::VK_SHARES; -use coconut_dkg_common::types::EpochState; -use coconut_dkg_common::verification_key::{to_cosmos_msg, ContractVKShare, VerificationKeyShare}; +use crate::verification_key_shares::storage::vk_shares; use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response}; +use nym_coconut_dkg_common::types::EpochState; +use nym_coconut_dkg_common::verification_key::{ + to_cosmos_msg, ContractVKShare, VerificationKeyShare, +}; pub fn try_commit_verification_key_share( deps: DepsMut<'_>, env: Env, info: MessageInfo, share: VerificationKeyShare, + resharing: bool, ) -> Result { - check_epoch_state(deps.storage, EpochState::VerificationKeySubmission)?; + check_epoch_state( + deps.storage, + EpochState::VerificationKeySubmission { resharing }, + )?; // ensure the sender is a dealer let details = dealers_storage::current_dealers() .load(deps.storage, &info.sender) .map_err(|_| ContractError::NotADealer)?; - if VK_SHARES.may_load(deps.storage, &info.sender)?.is_some() { + let epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id; + if vk_shares() + .may_load(deps.storage, (&info.sender, epoch_id))? + .is_some() + { return Err(ContractError::AlreadyCommitted { commitment: String::from("verification key share"), }); @@ -33,12 +44,14 @@ pub fn try_commit_verification_key_share( node_index: details.assigned_index, announce_address: details.announce_address, owner: info.sender.clone(), + epoch_id: CURRENT_EPOCH.load(deps.storage)?.epoch_id, verified: false, }; - VK_SHARES.save(deps.storage, &info.sender, &data)?; + vk_shares().save(deps.storage, (&info.sender, epoch_id), &data)?; let msg = to_cosmos_msg( info.sender, + resharing, env.contract.address.to_string(), STATE.load(deps.storage)?.multisig_addr.to_string(), env.block @@ -53,10 +66,15 @@ pub fn try_verify_verification_key_share( deps: DepsMut<'_>, info: MessageInfo, owner: Addr, + resharing: bool, ) -> Result { - check_epoch_state(deps.storage, EpochState::VerificationKeyFinalization)?; + check_epoch_state( + deps.storage, + EpochState::VerificationKeyFinalization { resharing }, + )?; + let epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id; MULTISIG.assert_admin(deps.as_ref(), &info.sender)?; - VK_SHARES.update(deps.storage, &owner, |vk_share| { + vk_shares().update(deps.storage, (&owner, epoch_id), |vk_share| { vk_share .map(|mut share| { share.verified = true; @@ -73,20 +91,70 @@ pub fn try_verify_verification_key_share( #[cfg(test)] mod tests { use super::*; - use crate::epoch_state::storage::CURRENT_EPOCH_STATE; use crate::epoch_state::transactions::advance_epoch_state; use crate::support::tests::helpers; - use crate::support::tests::helpers::{ADMIN_ADDRESS, MULTISIG_CONTRACT}; - use coconut_dkg_common::dealer::DealerDetails; - use coconut_dkg_common::types::EpochState; + use crate::support::tests::helpers::{add_fixture_dealer, MULTISIG_CONTRACT}; use cosmwasm_std::testing::{mock_env, mock_info}; - use cosmwasm_std::Storage; use cw_controllers::AdminError; + use nym_coconut_dkg_common::dealer::DealerDetails; + use nym_coconut_dkg_common::types::{EpochState, TimeConfiguration}; + + #[test] + fn current_epoch_id() { + let mut deps = helpers::init_contract(); + let mut env = mock_env(); + let info = mock_info("requester", &[]); + let share = "share".to_string(); + + add_fixture_dealer(deps.as_mut()); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let dealer = Addr::unchecked("requester"); + let announce_address = String::from("localhost"); + let dealer_details = DealerDetails { + address: dealer.clone(), + bte_public_key_with_proof: String::new(), + announce_address: announce_address.clone(), + assigned_index: 1, + }; + dealers_storage::current_dealers() + .save(deps.as_mut().storage, &dealer, &dealer_details) + .unwrap(); + + try_commit_verification_key_share( + deps.as_mut(), + env.clone(), + info.clone(), + share.clone(), + false, + ) + .unwrap(); + let vk_share = vk_shares().load(&deps.storage, (&info.sender, 0)).unwrap(); + assert_eq!( + vk_share, + ContractVKShare { + share, + announce_address, + node_index: 1, + owner: dealer, + epoch_id: 0, + verified: false, + } + ); + } #[test] fn commit_vk_share() { let mut deps = helpers::init_contract(); - let env = mock_env(); + let mut env = mock_env(); let info = mock_info("requester", &[]); let share = "share".to_string(); @@ -95,22 +163,34 @@ mod tests { env.clone(), info.clone(), share.clone(), + false, ) .unwrap_err(); assert_eq!( ret, ContractError::IncorrectEpochState { current_state: EpochState::default().to_string(), - expected_state: EpochState::VerificationKeySubmission.to_string() + expected_state: EpochState::VerificationKeySubmission { resharing: false } + .to_string() } ); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + add_fixture_dealer(deps.as_mut()); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let ret = try_commit_verification_key_share( deps.as_mut(), env.clone(), info.clone(), share.clone(), + false, ) .unwrap_err(); assert_eq!(ret, ContractError::NotADealer); @@ -126,14 +206,21 @@ mod tests { .save(deps.as_mut().storage, &dealer, &dealer_details) .unwrap(); - try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone()) - .unwrap(); + try_commit_verification_key_share( + deps.as_mut(), + env.clone(), + info.clone(), + share.clone(), + false, + ) + .unwrap(); let ret = try_commit_verification_key_share( deps.as_mut(), env.clone(), info.clone(), share.clone(), + false, ) .unwrap_err(); assert_eq!( @@ -147,31 +234,52 @@ mod tests { #[test] fn invalid_verify_vk_share() { let mut deps = helpers::init_contract(); + let mut env = mock_env(); let info = mock_info("requester", &[]); let owner = Addr::unchecked("owner"); let multisig_info = mock_info(MULTISIG_CONTRACT, &[]); - let ret = try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone()) - .unwrap_err(); + let ret = + try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone(), false) + .unwrap_err(); assert_eq!( ret, ContractError::IncorrectEpochState { current_state: EpochState::default().to_string(), - expected_state: EpochState::VerificationKeyFinalization.to_string() + expected_state: EpochState::VerificationKeyFinalization { resharing: false } + .to_string() } ); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + add_fixture_dealer(deps.as_mut()); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().verification_key_submission_time_secs); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs); + advance_epoch_state(deps.as_mut(), env).unwrap(); - let ret = - try_verify_verification_key_share(deps.as_mut(), info, owner.clone()).unwrap_err(); + let ret = try_verify_verification_key_share(deps.as_mut(), info, owner.clone(), false) + .unwrap_err(); assert_eq!(ret, ContractError::Admin(AdminError::NotAdmin {})); - let ret = try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone()) - .unwrap_err(); + let ret = + try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false) + .unwrap_err(); assert_eq!( ret, ContractError::NoCommitForOwner { @@ -183,14 +291,23 @@ mod tests { #[test] fn verify_vk_share() { let mut deps = helpers::init_contract(); - let env = mock_env(); + let mut env = mock_env(); let owner = Addr::unchecked("owner"); let info = mock_info(owner.as_ref(), &[]); let share = "share".to_string(); let multisig_info = mock_info(MULTISIG_CONTRACT, &[]); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + add_fixture_dealer(deps.as_mut()); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().dealing_exchange_time_secs); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let dealer_details = DealerDetails { address: owner.clone(), @@ -201,12 +318,27 @@ mod tests { dealers_storage::current_dealers() .save(deps.as_mut().storage, &owner, &dealer_details) .unwrap(); - try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone()) + try_commit_verification_key_share( + deps.as_mut(), + env.clone(), + info.clone(), + share.clone(), + false, + ) + .unwrap(); + + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().verification_key_submission_time_secs); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs); + advance_epoch_state(deps.as_mut(), env).unwrap(); + + try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false) .unwrap(); - - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - - try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone()).unwrap(); } } diff --git a/contracts/coconut-test/Cargo.toml b/contracts/coconut-test/Cargo.toml index f9dd795e4b..2a0eb5cb3f 100644 --- a/contracts/coconut-test/Cargo.toml +++ b/contracts/coconut-test/Cargo.toml @@ -2,30 +2,32 @@ name = "coconut-test" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" } -coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } -coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } -multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } +nym-bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" } +nym-coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } +nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } +nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } +nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" } -cosmwasm-std = "1.0.0" -cosmwasm-storage = "1.0.0" -cw3 = "0.13.4" -cw4 = "0.13.4" -cw-storage-plus = "0.13.4" -cw-controllers = "0.13.4" -cw-utils = "0.13.4" +cosmwasm-std = { workspace = true } +cosmwasm-storage = { workspace = true } +cw3 = { workspace = true } +cw4 = { workspace = true } +cw-storage-plus = { workspace = true } +cw-controllers = { workspace = true } +cw-utils = { workspace = true } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = "1.0.23" -coconut-bandwidth = { path = "../coconut-bandwidth" } -coconut-dkg = { path = "../coconut-dkg" } -cw-multi-test = { version = "0.13.4" } +nym-coconut-bandwidth = { path = "../coconut-bandwidth" } +nym-coconut-dkg = { path = "../coconut-dkg" } +cw-multi-test = { workspace = true } cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" } cw4-group = { path = "../multisig/cw4-group" } diff --git a/contracts/coconut-test/src/deposit_and_release.rs b/contracts/coconut-test/src/deposit_and_release.rs index 0212741261..374b14e3a6 100644 --- a/contracts/coconut-test/src/deposit_and_release.rs +++ b/contracts/coconut-test/src/deposit_and_release.rs @@ -2,14 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use crate::helpers::*; -use coconut_bandwidth::error::ContractError; -use coconut_bandwidth_contract_common::{ - deposit::DepositData, - msg::{ExecuteMsg, InstantiateMsg}, -}; use cosmwasm_std::{coins, Addr}; use cw_controllers::AdminError; use cw_multi_test::Executor; +use nym_coconut_bandwidth::error::ContractError; +use nym_coconut_bandwidth_contract_common::{ + deposit::DepositData, + msg::{ExecuteMsg, InstantiateMsg}, +}; const TEST_MIX_DENOM: &str = "unym"; diff --git a/contracts/coconut-test/src/helpers.rs b/contracts/coconut-test/src/helpers.rs index 319e523c91..c679176177 100644 --- a/contracts/coconut-test/src/helpers.rs +++ b/contracts/coconut-test/src/helpers.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response}; -use cw3_flex_multisig::{state::CONFIG, ContractError}; +use cw3_flex_multisig::state::CONFIG; use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper}; +use nym_multisig_contract_common::error::ContractError; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -42,18 +43,18 @@ pub fn mock_app(init_funds: &[Coin]) -> App { } pub fn contract_dkg() -> Box> { let contract = ContractWrapper::new( - coconut_dkg::contract::execute, - coconut_dkg::contract::instantiate, - coconut_dkg::contract::query, + nym_coconut_dkg::contract::execute, + nym_coconut_dkg::contract::instantiate, + nym_coconut_dkg::contract::query, ); Box::new(contract) } pub fn contract_bandwidth() -> Box> { let contract = ContractWrapper::new( - coconut_bandwidth::contract::execute, - coconut_bandwidth::contract::instantiate, - coconut_bandwidth::contract::query, + nym_coconut_bandwidth::contract::execute, + nym_coconut_bandwidth::contract::instantiate, + nym_coconut_bandwidth::contract::query, ); Box::new(contract) } diff --git a/contracts/coconut-test/src/spend_credential_creates_proposal.rs b/contracts/coconut-test/src/spend_credential_creates_proposal.rs index 3d382f493b..12742ef049 100644 --- a/contracts/coconut-test/src/spend_credential_creates_proposal.rs +++ b/contracts/coconut-test/src/spend_credential_creates_proposal.rs @@ -1,16 +1,16 @@ use crate::helpers::*; -use coconut_bandwidth::error::ContractError; -use coconut_bandwidth_contract_common::{ +use cosmwasm_std::{coins, Addr, Coin, Decimal}; +use cw_multi_test::Executor; +use cw_utils::{Duration, Threshold}; +use nym_coconut_bandwidth::error::ContractError; +use nym_coconut_bandwidth_contract_common::{ msg::{ ExecuteMsg as CoconutBandwidthExecuteMsg, InstantiateMsg as CoconutBandwidthInstantiateMsg, }, spend_credential::SpendCredentialData, }; -use cosmwasm_std::{coins, Addr, Coin, Decimal}; -use cw4_group::msg::InstantiateMsg as GroupInstantiateMsg; -use cw_multi_test::Executor; -use cw_utils::{Duration, Threshold}; -use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg; +use nym_group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg; +use nym_multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg; pub const TEST_COIN_DENOM: &str = "unym"; pub const TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = diff --git a/contracts/coconut-test/src/submit_vk_creates_proposal.rs b/contracts/coconut-test/src/submit_vk_creates_proposal.rs index 15bbd65341..540a6d8c02 100644 --- a/contracts/coconut-test/src/submit_vk_creates_proposal.rs +++ b/contracts/coconut-test/src/submit_vk_creates_proposal.rs @@ -7,19 +7,19 @@ use crate::helpers::{ use crate::spend_credential_creates_proposal::{ TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS, TEST_COCONUT_DKG_CONTRACT_ADDRESS, TEST_COIN_DENOM, }; -use coconut_dkg_common::msg::ExecuteMsg::{ - AdvanceEpochState, CommitVerificationKeyShare, RegisterDealer, -}; -use coconut_dkg_common::msg::InstantiateMsg as DkgInstantiateMsg; -use coconut_dkg_common::msg::QueryMsg::GetVerificationKeys; -use coconut_dkg_common::verification_key::PagedVKSharesResponse; use cosmwasm_std::{coins, Addr, Decimal}; use cw4::Member; -use cw4_group::msg::InstantiateMsg as GroupInstantiateMsg; use cw_multi_test::Executor; use cw_utils::{Duration, Threshold}; -use multisig_contract_common::msg::ExecuteMsg::{Execute, Vote}; -use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg; +use nym_coconut_dkg_common::msg::ExecuteMsg::{ + AdvanceEpochState, CommitVerificationKeyShare, RegisterDealer, +}; +use nym_coconut_dkg_common::msg::InstantiateMsg as DkgInstantiateMsg; +use nym_coconut_dkg_common::msg::QueryMsg::GetVerificationKeys; +use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse; +use nym_group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg; +use nym_multisig_contract_common::msg::ExecuteMsg::{Execute, Vote}; +use nym_multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg; #[test] fn dkg_proposal() { @@ -71,7 +71,7 @@ fn dkg_proposal() { let msg = DkgInstantiateMsg { group_addr: group_contract_addr.to_string(), multisig_addr: multisig_contract_addr.to_string(), - admin: Addr::unchecked(OWNER).to_string(), + time_configuration: None, mix_denom: TEST_COIN_DENOM.to_string(), }; let coconut_dkg_contract_addr = app @@ -103,12 +103,14 @@ fn dkg_proposal() { &RegisterDealer { bte_key_with_proof: "bte_key_with_proof".to_string(), announce_address: "127.0.0.1:8000".to_string(), + resharing: false, }, &vec![], ) .unwrap(); for _ in 0..2 { + app.update_block(|block| block.time = block.time.plus_seconds(1000)); app.execute_contract( Addr::unchecked(OWNER), coconut_dkg_contract_addr.clone(), @@ -123,6 +125,7 @@ fn dkg_proposal() { let msg = CommitVerificationKeyShare { share: "share".to_string(), + resharing: false, }; let res = app .execute_contract( @@ -151,6 +154,7 @@ fn dkg_proposal() { .query_wasm_smart( coconut_dkg_contract_addr.clone(), &GetVerificationKeys { + epoch_id: 0, limit: None, start_after: None, }, @@ -175,6 +179,7 @@ fn dkg_proposal() { .unwrap(); for _ in 0..2 { + app.update_block(|block| block.time = block.time.plus_seconds(1000)); app.execute_contract( Addr::unchecked(OWNER), coconut_dkg_contract_addr.clone(), @@ -197,6 +202,7 @@ fn dkg_proposal() { .query_wasm_smart( coconut_dkg_contract_addr, &GetVerificationKeys { + epoch_id: 0, limit: None, start_after: None, }, diff --git a/contracts/mixnet/.circleci/config.yml b/contracts/mixnet/.circleci/config.yml deleted file mode 100644 index a130fc72c9..0000000000 --- a/contracts/mixnet/.circleci/config.yml +++ /dev/null @@ -1,49 +0,0 @@ -version: 2.1 - -jobs: - build: - docker: - - image: rust:1.44.1 - steps: - - checkout - - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown - - run: - name: Unit tests - env: RUST_BACKTRACE=1 - command: cargo unit-test --locked - - run: - name: Build - command: cargo wasm --locked - - run: - name: Format source code - command: cargo fmt - - run: - name: Build and run schema generator - command: cargo schema --locked - - run: - name: Ensure checked-in source code and schemas are up-to-date - command: | - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - - target/wasm32-unknown-unknown/release/.fingerprint - - target/wasm32-unknown-unknown/release/build - - target/wasm32-unknown-unknown/release/deps - key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }} diff --git a/contracts/mixnet/.editorconfig b/contracts/mixnet/.editorconfig deleted file mode 100644 index 3d36f20b19..0000000000 --- a/contracts/mixnet/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.rs] -indent_size = 4 diff --git a/contracts/mixnet/.gitignore b/contracts/mixnet/.gitignore deleted file mode 100644 index 1761c01d05..0000000000 --- a/contracts/mixnet/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.envrc \ No newline at end of file diff --git a/contracts/mixnet/Cargo.toml b/contracts/mixnet/Cargo.toml index 6dc7273c18..9f180929d3 100644 --- a/contracts/mixnet/Cargo.toml +++ b/contracts/mixnet/Cargo.toml @@ -1,44 +1,56 @@ [package] -name = "mixnet-contract" -version = "1.1.2" -authors = ["Dave Hrycyszyn "] -edition = "2021" +name = "nym-mixnet-contract" +version = "1.2.0" +description = "Nym mixnet contract" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +readme = "README.md" exclude = [ # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. "contract.wasm", "hash.txt", + "artifacts", ] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] +name = "mixnet_contract" crate-type = ["cdylib", "rlib"] [dependencies] -mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } -vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } -#config = { path = "../../common/config"} +mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.3.0" } +vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.3.0" } +#nym-config = { path = "../../common/config"} -cosmwasm-std = "1.0.0" -cosmwasm-storage = "1.0.0" -cw-storage-plus = "0.13.4" +cosmwasm-std = { workspace = true } +cosmwasm-storage = { workspace = true } +cosmwasm-derive = { workspace = true } +cw2 = { workspace = true } +cw-storage-plus = { workspace = true } bs58 = "0.4.0" schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } time = { version = "0.3", features = ["macros"] } +semver = { version = "1.0.16", default-features = false } [dev-dependencies] -cosmwasm-schema = "1.0.0" +cosmwasm-schema = { workspace = true } rand_chacha = "0.2" #rand = "0.7" -crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } [build-dependencies] -vergen = { version = "5", default-features = false, features = ["build", "git", "rustc"] } +vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] } [features] default = [] contract-testing = ["mixnet-contract-common/contract-testing"] + +[profile.release] +overflow-checks = true diff --git a/contracts/mixnet/build.rs b/contracts/mixnet/build.rs index 01b3a20dc6..93402c2fbd 100644 --- a/contracts/mixnet/build.rs +++ b/contracts/mixnet/build.rs @@ -4,5 +4,10 @@ use vergen::{vergen, Config}; fn main() { - vergen(Config::default()).expect("failed to extract build metadata") + let mut config = Config::default(); + if std::env::var("DOCS_RS").is_ok() { + // If we don't have access to git information, such as in a docs.rs build, don't error + *config.git_mut().skip_if_error_mut() = true; + } + vergen(config).expect("failed to extract build metadata"); } diff --git a/contracts/mixnet/examples/schema.rs b/contracts/mixnet/examples/schema.rs index 0571f23914..46e555aff3 100644 --- a/contracts/mixnet/examples/schema.rs +++ b/contracts/mixnet/examples/schema.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use mixnet_contract_common::{ExecuteMsg, InstantiateMsg, MixNodeBond, QueryMsg}; +use mixnet_contract_common::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; use std::env::current_dir; use std::fs::create_dir_all; @@ -15,5 +15,5 @@ fn main() { export_schema(&schema_for!(InstantiateMsg), &out_dir); export_schema(&schema_for!(ExecuteMsg), &out_dir); export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema(&schema_for!(MixNodeBond), &out_dir); + export_schema(&schema_for!(MigrateMsg), &out_dir); } diff --git a/contracts/mixnet/schema/execute_msg.json b/contracts/mixnet/schema/execute_msg.json index 377975435f..b50d74a447 100644 --- a/contracts/mixnet/schema/execute_msg.json +++ b/contracts/mixnet/schema/execute_msg.json @@ -5,41 +5,23 @@ { "type": "object", "required": [ - "reconcile_delegations" + "assign_node_layer" ], "properties": { - "reconcile_delegations": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "checkpoint_mixnodes" - ], - "properties": { - "checkpoint_mixnodes": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "compound_operator_reward_on_behalf" - ], - "properties": { - "compound_operator_reward_on_behalf": { + "assign_node_layer": { "type": "object", "required": [ - "owner" + "layer", + "mix_id" ], "properties": { - "owner": { - "type": "string" + "layer": { + "$ref": "#/definitions/Layer" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 } } } @@ -47,22 +29,48 @@ "additionalProperties": false }, { + "description": "Only owner of the node can crate the family with node as head", "type": "object", "required": [ - "compound_delegator_reward_on_behalf" + "create_family" ], "properties": { - "compound_delegator_reward_on_behalf": { + "create_family": { "type": "object", "required": [ - "mix_identity", - "owner" + "label", + "owner_signature" ], "properties": { - "mix_identity": { + "label": { "type": "string" }, - "owner": { + "owner_signature": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Family head needs to sign the joining node IdentityKey", + "type": "object", + "required": [ + "join_family" + ], + "properties": { + "join_family": { + "type": "object", + "required": [ + "family_head", + "signature" + ], + "properties": { + "family_head": { + "type": "string" + }, + "signature": { "type": "string" } } @@ -73,11 +81,23 @@ { "type": "object", "required": [ - "compound_operator_reward" + "leave_family" ], "properties": { - "compound_operator_reward": { - "type": "object" + "leave_family": { + "type": "object", + "required": [ + "family_head", + "signature" + ], + "properties": { + "family_head": { + "type": "string" + }, + "signature": { + "type": "string" + } + } } }, "additionalProperties": false @@ -85,17 +105,307 @@ { "type": "object", "required": [ - "compound_delegator_reward" + "kick_family_member" ], "properties": { - "compound_delegator_reward": { + "kick_family_member": { "type": "object", "required": [ - "mix_identity" + "member", + "signature" ], "properties": { - "mix_identity": { + "member": { "type": "string" + }, + "signature": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "create_family_on_behalf" + ], + "properties": { + "create_family_on_behalf": { + "type": "object", + "required": [ + "label", + "owner_address", + "owner_signature" + ], + "properties": { + "label": { + "type": "string" + }, + "owner_address": { + "type": "string" + }, + "owner_signature": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Family head needs to sign the joining node IdentityKey", + "type": "object", + "required": [ + "join_family_on_behalf" + ], + "properties": { + "join_family_on_behalf": { + "type": "object", + "required": [ + "family_head", + "member_address", + "signature" + ], + "properties": { + "family_head": { + "type": "string" + }, + "member_address": { + "type": "string" + }, + "signature": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "leave_family_on_behalf" + ], + "properties": { + "leave_family_on_behalf": { + "type": "object", + "required": [ + "family_head", + "member_address", + "signature" + ], + "properties": { + "family_head": { + "type": "string" + }, + "member_address": { + "type": "string" + }, + "signature": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "kick_family_member_on_behalf" + ], + "properties": { + "kick_family_member_on_behalf": { + "type": "object", + "required": [ + "head_address", + "member", + "signature" + ], + "properties": { + "head_address": { + "type": "string" + }, + "member": { + "type": "string" + }, + "signature": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_rewarding_validator_address" + ], + "properties": { + "update_rewarding_validator_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_contract_state_params" + ], + "properties": { + "update_contract_state_params": { + "type": "object", + "required": [ + "updated_parameters" + ], + "properties": { + "updated_parameters": { + "$ref": "#/definitions/ContractStateParams" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_active_set_size" + ], + "properties": { + "update_active_set_size": { + "type": "object", + "required": [ + "active_set_size", + "force_immediately" + ], + "properties": { + "active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "force_immediately": { + "type": "boolean" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_rewarding_params" + ], + "properties": { + "update_rewarding_params": { + "type": "object", + "required": [ + "force_immediately", + "updated_params" + ], + "properties": { + "force_immediately": { + "type": "boolean" + }, + "updated_params": { + "$ref": "#/definitions/IntervalRewardingParamsUpdate" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_interval_config" + ], + "properties": { + "update_interval_config": { + "type": "object", + "required": [ + "epoch_duration_secs", + "epochs_in_interval", + "force_immediately" + ], + "properties": { + "epoch_duration_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "epochs_in_interval": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "force_immediately": { + "type": "boolean" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "advance_current_epoch" + ], + "properties": { + "advance_current_epoch": { + "type": "object", + "required": [ + "expected_active_set_size", + "new_rewarded_set" + ], + "properties": { + "expected_active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "new_rewarded_set": { + "type": "array", + "items": { + "$ref": "#/definitions/LayerAssignment" + } + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reconcile_epoch_events" + ], + "properties": { + "reconcile_epoch_events": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 } } } @@ -111,10 +421,14 @@ "bond_mixnode": { "type": "object", "required": [ + "cost_params", "mix_node", "owner_signature" ], "properties": { + "cost_params": { + "$ref": "#/definitions/MixNodeCostParams" + }, "mix_node": { "$ref": "#/definitions/MixNode" }, @@ -126,6 +440,70 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "bond_mixnode_on_behalf" + ], + "properties": { + "bond_mixnode_on_behalf": { + "type": "object", + "required": [ + "cost_params", + "mix_node", + "owner", + "owner_signature" + ], + "properties": { + "cost_params": { + "$ref": "#/definitions/MixNodeCostParams" + }, + "mix_node": { + "$ref": "#/definitions/MixNode" + }, + "owner": { + "type": "string" + }, + "owner_signature": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pledge_more" + ], + "properties": { + "pledge_more": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pledge_more_on_behalf" + ], + "properties": { + "pledge_more_on_behalf": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -138,6 +516,70 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "unbond_mixnode_on_behalf" + ], + "properties": { + "unbond_mixnode_on_behalf": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnode_cost_params" + ], + "properties": { + "update_mixnode_cost_params": { + "type": "object", + "required": [ + "new_costs" + ], + "properties": { + "new_costs": { + "$ref": "#/definitions/MixNodeCostParams" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_mixnode_cost_params_on_behalf" + ], + "properties": { + "update_mixnode_cost_params_on_behalf": { + "type": "object", + "required": [ + "new_costs", + "owner" + ], + "properties": { + "new_costs": { + "$ref": "#/definitions/MixNodeCostParams" + }, + "owner": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -147,13 +589,11 @@ "update_mixnode_config": { "type": "object", "required": [ - "profit_margin_percent" + "new_config" ], "properties": { - "profit_margin_percent": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 + "new_config": { + "$ref": "#/definitions/MixNodeConfigUpdate" } } } @@ -169,17 +609,15 @@ "update_mixnode_config_on_behalf": { "type": "object", "required": [ - "owner", - "profit_margin_percent" + "new_config", + "owner" ], "properties": { + "new_config": { + "$ref": "#/definitions/MixNodeConfigUpdate" + }, "owner": { "type": "string" - }, - "profit_margin_percent": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 } } } @@ -210,190 +648,6 @@ }, "additionalProperties": false }, - { - "type": "object", - "required": [ - "unbond_gateway" - ], - "properties": { - "unbond_gateway": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "update_contract_state_params" - ], - "properties": { - "update_contract_state_params": { - "$ref": "#/definitions/ContractStateParams" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "delegate_to_mixnode" - ], - "properties": { - "delegate_to_mixnode": { - "type": "object", - "required": [ - "mix_identity" - ], - "properties": { - "mix_identity": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "undelegate_from_mixnode" - ], - "properties": { - "undelegate_from_mixnode": { - "type": "object", - "required": [ - "mix_identity" - ], - "properties": { - "mix_identity": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "reward_mixnode" - ], - "properties": { - "reward_mixnode": { - "type": "object", - "required": [ - "identity", - "params" - ], - "properties": { - "identity": { - "type": "string" - }, - "params": { - "$ref": "#/definitions/NodeRewardParams" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "delegate_to_mixnode_on_behalf" - ], - "properties": { - "delegate_to_mixnode_on_behalf": { - "type": "object", - "required": [ - "delegate", - "mix_identity" - ], - "properties": { - "delegate": { - "type": "string" - }, - "mix_identity": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "undelegate_from_mixnode_on_behalf" - ], - "properties": { - "undelegate_from_mixnode_on_behalf": { - "type": "object", - "required": [ - "delegate", - "mix_identity" - ], - "properties": { - "delegate": { - "type": "string" - }, - "mix_identity": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "bond_mixnode_on_behalf" - ], - "properties": { - "bond_mixnode_on_behalf": { - "type": "object", - "required": [ - "mix_node", - "owner", - "owner_signature" - ], - "properties": { - "mix_node": { - "$ref": "#/definitions/MixNode" - }, - "owner": { - "type": "string" - }, - "owner_signature": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "unbond_mixnode_on_behalf" - ], - "properties": { - "unbond_mixnode_on_behalf": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "owner": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, { "type": "object", "required": [ @@ -422,6 +676,18 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "unbond_gateway" + ], + "properties": { + "unbond_gateway": { + "type": "object" + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -445,26 +711,19 @@ { "type": "object", "required": [ - "write_rewarded_set" + "delegate_to_mixnode" ], "properties": { - "write_rewarded_set": { + "delegate_to_mixnode": { "type": "object", "required": [ - "expected_active_set_size", - "rewarded_set" + "mix_id" ], "properties": { - "expected_active_set_size": { + "mix_id": { "type": "integer", "format": "uint32", "minimum": 0.0 - }, - "rewarded_set": { - "type": "array", - "items": { - "type": "string" - } } } } @@ -474,44 +733,240 @@ { "type": "object", "required": [ - "advance_current_epoch" + "delegate_to_mixnode_on_behalf" ], "properties": { - "advance_current_epoch": { + "delegate_to_mixnode_on_behalf": { + "type": "object", + "required": [ + "delegate", + "mix_id" + ], + "properties": { + "delegate": { + "type": "string" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_mixnode" + ], + "properties": { + "undelegate_from_mixnode": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_mixnode_on_behalf" + ], + "properties": { + "undelegate_from_mixnode_on_behalf": { + "type": "object", + "required": [ + "delegate", + "mix_id" + ], + "properties": { + "delegate": { + "type": "string" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reward_mixnode" + ], + "properties": { + "reward_mixnode": { + "type": "object", + "required": [ + "mix_id", + "performance" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "performance": { + "$ref": "#/definitions/Percent" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_operator_reward" + ], + "properties": { + "withdraw_operator_reward": { "type": "object" } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_operator_reward_on_behalf" + ], + "properties": { + "withdraw_operator_reward_on_behalf": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "owner": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_delegator_reward_on_behalf" + ], + "properties": { + "withdraw_delegator_reward_on_behalf": { + "type": "object", + "required": [ + "mix_id", + "owner" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + } + } + } + }, + "additionalProperties": false } ], "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, "ContractStateParams": { "type": "object", "required": [ "minimum_gateway_pledge", - "minimum_mixnode_pledge", - "mixnode_active_set_size", - "mixnode_rewarded_set_size" + "minimum_mixnode_pledge" ], "properties": { "minimum_gateway_pledge": { - "$ref": "#/definitions/Uint128" + "description": "Minimum amount a gateway must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "minimum_mixnode_delegation": { + "description": "Minimum amount a delegator must stake in orders for his delegation to get accepted.", + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] }, "minimum_mixnode_pledge": { - "$ref": "#/definitions/Uint128" - }, - "mixnode_active_set_size": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "mixnode_rewarded_set_size": { - "type": "integer", - "format": "uint32", - "minimum": 0.0 + "description": "Minimum amount a mixnode must pledge to get into the system.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] } } }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "Gateway": { "type": "object", "required": [ @@ -552,6 +1007,104 @@ } } }, + "IntervalRewardingParamsUpdate": { + "type": "object", + "properties": { + "active_set_work_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "interval_pool_emission": { + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "reward_pool": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "rewarded_set_size": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "staking_supply": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "staking_supply_scale_factor": { + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + }, + "sybil_resistance_percent": { + "anyOf": [ + { + "$ref": "#/definitions/Percent" + }, + { + "type": "null" + } + ] + } + } + }, + "Layer": { + "type": "string", + "enum": [ + "One", + "Two", + "Three" + ] + }, + "LayerAssignment": { + "type": "object", + "required": [ + "layer", + "mix_id" + ], + "properties": { + "layer": { + "$ref": "#/definitions/Layer" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, "MixNode": { "type": "object", "required": [ @@ -559,13 +1112,13 @@ "http_api_port", "identity_key", "mix_port", - "profit_margin_percent", "sphinx_key", "verloc_port", "version" ], "properties": { "host": { + "description": "Network address of this mixnode, for example 1.1.1.1:1234 or foo.mixnode.com", "type": "string" }, "http_api_port": { @@ -574,7 +1127,7 @@ "minimum": 0.0 }, "identity_key": { - "description": "Base58 encoded ed25519 EdDSA public key.", + "description": "Base58-encoded ed25519 EdDSA public key.", "type": "string" }, "mix_port": { @@ -582,12 +1135,8 @@ "format": "uint16", "minimum": 0.0 }, - "profit_margin_percent": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - }, "sphinx_key": { + "description": "Base58-encoded x25519 public key used for sphinx key derivation.", "type": "string" }, "verloc_port": { @@ -600,27 +1149,67 @@ } } }, - "NodeRewardParams": { + "MixNodeConfigUpdate": { "type": "object", "required": [ - "in_active_set", - "reward_blockstamp", - "uptime" + "host", + "http_api_port", + "mix_port", + "verloc_port", + "version" ], "properties": { - "in_active_set": { - "type": "boolean" + "host": { + "type": "string" }, - "reward_blockstamp": { + "http_api_port": { "type": "integer", - "format": "uint64", + "format": "uint16", "minimum": 0.0 }, - "uptime": { - "$ref": "#/definitions/Uint128" + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "verloc_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "type": "string" } } }, + "MixNodeCostParams": { + "type": "object", + "required": [ + "interval_operating_cost", + "profit_margin_percent" + ], + "properties": { + "interval_operating_cost": { + "description": "Operating cost of the associated mixnode per the entire interval.", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "profit_margin_percent": { + "$ref": "#/definitions/Percent" + } + } + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/contracts/mixnet/schema/instantiate_msg.json b/contracts/mixnet/schema/instantiate_msg.json index 3aa0e29b6d..6d7c0ea33a 100644 --- a/contracts/mixnet/schema/instantiate_msg.json +++ b/contracts/mixnet/schema/instantiate_msg.json @@ -3,11 +3,109 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "rewarding_validator_address" + "epoch_duration", + "epochs_in_interval", + "initial_rewarding_params", + "rewarding_denom", + "rewarding_validator_address", + "vesting_contract_address" ], "properties": { + "epoch_duration": { + "$ref": "#/definitions/Duration" + }, + "epochs_in_interval": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "initial_rewarding_params": { + "$ref": "#/definitions/InitialRewardingParams" + }, + "rewarding_denom": { + "type": "string" + }, "rewarding_validator_address": { "type": "string" + }, + "vesting_contract_address": { + "type": "string" + } + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Duration": { + "type": "object", + "required": [ + "nanos", + "secs" + ], + "properties": { + "nanos": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "InitialRewardingParams": { + "type": "object", + "required": [ + "active_set_size", + "active_set_work_factor", + "initial_reward_pool", + "initial_staking_supply", + "interval_pool_emission", + "rewarded_set_size", + "staking_supply_scale_factor", + "sybil_resistance" + ], + "properties": { + "active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "active_set_work_factor": { + "$ref": "#/definitions/Decimal" + }, + "initial_reward_pool": { + "$ref": "#/definitions/Decimal" + }, + "initial_staking_supply": { + "$ref": "#/definitions/Decimal" + }, + "interval_pool_emission": { + "$ref": "#/definitions/Percent" + }, + "rewarded_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "staking_supply_scale_factor": { + "$ref": "#/definitions/Percent" + }, + "sybil_resistance": { + "$ref": "#/definitions/Percent" + } + } + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] } } } diff --git a/contracts/mixnet/schema/migrate_msg.json b/contracts/mixnet/schema/migrate_msg.json new file mode 100644 index 0000000000..d1975efa64 --- /dev/null +++ b/contracts/mixnet/schema/migrate_msg.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object", + "properties": { + "vesting_contract_address": { + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/mixnet/schema/mix_node_bond.json b/contracts/mixnet/schema/mix_node_bond.json deleted file mode 100644 index 0f5c3fd0be..0000000000 --- a/contracts/mixnet/schema/mix_node_bond.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MixNodeBond", - "type": "object", - "required": [ - "block_height", - "layer", - "mix_node", - "owner", - "pledge_amount", - "total_delegation" - ], - "properties": { - "accumulated_rewards": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "block_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "layer": { - "$ref": "#/definitions/Layer" - }, - "mix_node": { - "$ref": "#/definitions/MixNode" - }, - "owner": { - "$ref": "#/definitions/Addr" - }, - "pledge_amount": { - "$ref": "#/definitions/Coin" - }, - "proxy": { - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } - ] - }, - "total_delegation": { - "$ref": "#/definitions/Coin" - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Layer": { - "type": "string", - "enum": [ - "Gateway", - "One", - "Two", - "Three" - ] - }, - "MixNode": { - "type": "object", - "required": [ - "host", - "http_api_port", - "identity_key", - "mix_port", - "profit_margin_percent", - "sphinx_key", - "verloc_port", - "version" - ], - "properties": { - "host": { - "type": "string" - }, - "http_api_port": { - "type": "integer", - "format": "uint16", - "minimum": 0.0 - }, - "identity_key": { - "description": "Base58 encoded ed25519 EdDSA public key.", - "type": "string" - }, - "mix_port": { - "type": "integer", - "format": "uint16", - "minimum": 0.0 - }, - "profit_margin_percent": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - }, - "sphinx_key": { - "type": "string" - }, - "verloc_port": { - "type": "integer", - "format": "uint16", - "minimum": 0.0 - }, - "version": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/mixnet/schema/query_msg.json b/contracts/mixnet/schema/query_msg.json index 43ab824a4c..a8dde27e0a 100644 --- a/contracts/mixnet/schema/query_msg.json +++ b/contracts/mixnet/schema/query_msg.json @@ -5,22 +5,10 @@ { "type": "object", "required": [ - "get_contract_version" + "get_all_families_paged" ], "properties": { - "get_contract_version": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_mix_nodes" - ], - "properties": { - "get_mix_nodes": { + "get_all_families_paged": { "type": "object", "properties": { "limit": { @@ -42,6 +30,518 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "get_all_members_paged" + ], + "properties": { + "get_all_members_paged": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_family_by_head" + ], + "properties": { + "get_family_by_head": { + "type": "object", + "required": [ + "head" + ], + "properties": { + "head": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_family_by_label" + ], + "properties": { + "get_family_by_label": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_family_members_by_head" + ], + "properties": { + "get_family_members_by_head": { + "type": "object", + "required": [ + "head" + ], + "properties": { + "head": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_family_members_by_label" + ], + "properties": { + "get_family_members_by_label": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_contract_version" + ], + "properties": { + "get_contract_version": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_rewarding_validator_address" + ], + "properties": { + "get_rewarding_validator_address": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_state_params" + ], + "properties": { + "get_state_params": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_state" + ], + "properties": { + "get_state": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_rewarding_params" + ], + "properties": { + "get_rewarding_params": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_current_interval_details" + ], + "properties": { + "get_current_interval_details": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_rewarded_set" + ], + "properties": { + "get_rewarded_set": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_mix_node_bonds" + ], + "properties": { + "get_mix_node_bonds": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_mix_nodes_detailed" + ], + "properties": { + "get_mix_nodes_detailed": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_unbonded_mix_nodes" + ], + "properties": { + "get_unbonded_mix_nodes": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_unbonded_mix_nodes_by_owner" + ], + "properties": { + "get_unbonded_mix_nodes_by_owner": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_unbonded_mix_nodes_by_identity_key" + ], + "properties": { + "get_unbonded_mix_nodes_by_identity_key": { + "type": "object", + "required": [ + "identity_key" + ], + "properties": { + "identity_key": { + "type": "string" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_owned_mixnode" + ], + "properties": { + "get_owned_mixnode": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_mixnode_details" + ], + "properties": { + "get_mixnode_details": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_mixnode_rewarding_details" + ], + "properties": { + "get_mixnode_rewarding_details": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_stake_saturation" + ], + "properties": { + "get_stake_saturation": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_unbonded_mix_node_information" + ], + "properties": { + "get_unbonded_mix_node_information": { + "type": "object", + "required": [ + "mix_id" + ], + "properties": { + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_bonded_mixnode_details_by_identity" + ], + "properties": { + "get_bonded_mixnode_details_by_identity": { + "type": "object", + "required": [ + "mix_identity" + ], + "properties": { + "mix_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_layer_distribution" + ], + "properties": { + "get_layer_distribution": { + "type": "object" + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -73,16 +573,16 @@ { "type": "object", "required": [ - "owns_mixnode" + "get_gateway_bond" ], "properties": { - "owns_mixnode": { + "get_gateway_bond": { "type": "object", "required": [ - "address" + "identity" ], "properties": { - "address": { + "identity": { "type": "string" } } @@ -93,10 +593,10 @@ { "type": "object", "required": [ - "owns_gateway" + "get_owned_gateway" ], "properties": { - "owns_gateway": { + "get_owned_gateway": { "type": "object", "required": [ "address" @@ -110,66 +610,6 @@ }, "additionalProperties": false }, - { - "type": "object", - "required": [ - "state_params" - ], - "properties": { - "state_params": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_all_network_delegations" - ], - "properties": { - "get_all_network_delegations": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "array", - "null" - ], - "items": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - ], - "maxItems": 3, - "minItems": 3 - } - } - } - }, - "additionalProperties": false - }, { "type": "object", "required": [ @@ -179,7 +619,7 @@ "get_mixnode_delegations": { "type": "object", "required": [ - "mix_identity" + "mix_id" ], "properties": { "limit": { @@ -190,26 +630,16 @@ "format": "uint32", "minimum": 0.0 }, - "mix_identity": { - "type": "string" + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 }, "start_after": { "type": [ - "array", + "string", "null" - ], - "items": [ - { - "type": "string" - }, - { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - ], - "maxItems": 2, - "minItems": 2 + ] } } } @@ -241,9 +671,21 @@ }, "start_after": { "type": [ - "string", + "array", "null" - ] + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 } } } @@ -260,144 +702,18 @@ "type": "object", "required": [ "delegator", - "mix_identity" + "mix_id" ], "properties": { "delegator": { "type": "string" }, - "mix_identity": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "layer_distribution" - ], - "properties": { - "layer_distribution": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_reward_pool" - ], - "properties": { - "get_reward_pool": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_circulating_supply" - ], - "properties": { - "get_circulating_supply": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_interval_reward_percent" - ], - "properties": { - "get_interval_reward_percent": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_sybil_resistance_percent" - ], - "properties": { - "get_sybil_resistance_percent": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_active_set_work_factor" - ], - "properties": { - "get_active_set_work_factor": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_rewarding_status" - ], - "properties": { - "get_rewarding_status": { - "type": "object", - "required": [ - "interval_id", - "mix_identity" - ], - "properties": { - "interval_id": { + "mix_id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, - "mix_identity": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_rewarded_set" - ], - "properties": { - "get_rewarded_set": { - "type": "object", - "properties": { - "height": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { + "proxy": { "type": [ "string", "null" @@ -411,11 +727,39 @@ { "type": "object", "required": [ - "get_rewarded_set_update_details" + "get_all_delegations" ], "properties": { - "get_rewarded_set_update_details": { - "type": "object" + "get_all_delegations": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "array", + "null" + ], + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "string" + } + ], + "maxItems": 2, + "minItems": 2 + } + } } }, "additionalProperties": false @@ -423,58 +767,10 @@ { "type": "object", "required": [ - "get_current_rewarded_set_height" + "get_pending_operator_reward" ], "properties": { - "get_current_rewarded_set_height": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_rewarded_set_refresh_blocks" - ], - "properties": { - "get_rewarded_set_refresh_blocks": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_current_epoch" - ], - "properties": { - "get_current_epoch": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_epochs_in_interval" - ], - "properties": { - "get_epochs_in_interval": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_operator_reward" - ], - "properties": { - "query_operator_reward": { + "get_pending_operator_reward": { "type": "object", "required": [ "address" @@ -491,21 +787,19 @@ { "type": "object", "required": [ - "query_delegator_reward" + "get_pending_mix_node_operator_reward" ], "properties": { - "query_delegator_reward": { + "get_pending_mix_node_operator_reward": { "type": "object", "required": [ - "address", - "mix_identity" + "mix_id" ], "properties": { - "address": { - "type": "string" - }, - "mix_identity": { - "type": "string" + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 } } } @@ -515,22 +809,170 @@ { "type": "object", "required": [ - "get_pending_delegation_events" + "get_pending_delegator_reward" ], "properties": { - "get_pending_delegation_events": { + "get_pending_delegator_reward": { "type": "object", "required": [ - "owner_address" + "address", + "mix_id" ], "properties": { - "owner_address": { + "address": { "type": "string" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "proxy": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_estimated_current_epoch_operator_reward" + ], + "properties": { + "get_estimated_current_epoch_operator_reward": { + "type": "object", + "required": [ + "estimated_performance", + "mix_id" + ], + "properties": { + "estimated_performance": { + "$ref": "#/definitions/Percent" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_estimated_current_epoch_delegator_reward" + ], + "properties": { + "get_estimated_current_epoch_delegator_reward": { + "type": "object", + "required": [ + "address", + "estimated_performance", + "mix_id" + ], + "properties": { + "address": { + "type": "string" + }, + "estimated_performance": { + "$ref": "#/definitions/Percent" + }, + "mix_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "proxy": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_pending_epoch_events" + ], + "properties": { + "get_pending_epoch_events": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_pending_interval_events" + ], + "properties": { + "get_pending_interval_events": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 } } } }, "additionalProperties": false } - ] + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Percent": { + "description": "Percent represents a value between 0 and 100% (i.e. between 0.0 and 1.0)", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + } } diff --git a/contracts/mixnet/src/constants.rs b/contracts/mixnet/src/constants.rs index 882b89def4..504544acaa 100644 --- a/contracts/mixnet/src/constants.rs +++ b/contracts/mixnet/src/constants.rs @@ -47,6 +47,7 @@ pub(crate) const GATEWAYS_PK_NAMESPACE: &str = "gt"; pub(crate) const GATEWAYS_OWNER_IDX_NAMESPACE: &str = "gto"; pub(crate) const REWARDED_SET_KEY: &str = "rs"; +pub(crate) const CURRENT_EPOCH_STATUS_KEY: &str = "ces"; pub(crate) const CURRENT_INTERVAL_KEY: &str = "ci"; pub(crate) const EPOCH_EVENT_ID_COUNTER_KEY: &str = "eic"; pub(crate) const INTERVAL_EVENT_ID_COUNTER_KEY: &str = "iic"; diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index ade53d7d89..923b7614b5 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -1,5 +1,6 @@ -// Copyright 2021-2022 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 + use crate::constants::{INITIAL_GATEWAY_PLEDGE_AMOUNT, INITIAL_MIXNODE_PLEDGE_AMOUNT}; use crate::interval::storage as interval_storage; use crate::mixnet_contract_settings::storage as mixnet_params_storage; @@ -7,11 +8,17 @@ use crate::mixnodes::storage as mixnode_storage; use crate::rewards::storage as rewards_storage; use cosmwasm_std::{ entry_point, to_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, + StdError, }; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::{ ContractState, ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg, QueryMsg, }; +use semver::Version; + +// version info for migration info +const CONTRACT_NAME: &str = "crate:nym-mixnet-contract"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); fn default_initial_state( owner: Addr, @@ -50,11 +57,19 @@ pub fn instantiate( info: MessageInfo, msg: InstantiateMsg, ) -> Result { + if msg.epochs_in_interval == 0 { + return Err(MixnetContractError::EpochsInIntervalZero); + } + + if msg.epoch_duration.as_secs() == 0 { + return Err(MixnetContractError::EpochDurationZero); + } + let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?; let vesting_contract_address = deps.api.addr_validate(&msg.vesting_contract_address)?; let state = default_initial_state( info.sender, - rewarding_validator_address, + rewarding_validator_address.clone(), msg.rewarding_denom, vesting_contract_address, ); @@ -64,10 +79,15 @@ pub fn instantiate( .initial_rewarding_params .into_rewarding_params(msg.epochs_in_interval)?; - interval_storage::initialise_storage(deps.storage, starting_interval)?; + interval_storage::initialise_storage( + deps.storage, + starting_interval, + rewarding_validator_address, + )?; mixnet_params_storage::initialise_storage(deps.storage, state)?; mixnode_storage::initialise_storage(deps.storage)?; rewards_storage::initialise_storage(deps.storage, reward_params)?; + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; Ok(Response::default()) } @@ -92,7 +112,9 @@ pub fn execute( ExecuteMsg::JoinFamily { signature, family_head, - } => crate::families::transactions::try_join_family(deps, info, signature, family_head), + } => { + crate::families::transactions::try_join_family(deps, info, None, signature, family_head) + } ExecuteMsg::LeaveFamily { signature, family_head, @@ -113,24 +135,26 @@ pub fn execute( ), ExecuteMsg::JoinFamilyOnBehalf { member_address, - signature, + node_identity_signature, + family_signature, family_head, } => crate::families::transactions::try_join_family_on_behalf( deps, info, member_address, - signature, + Some(node_identity_signature), + family_signature, family_head, ), ExecuteMsg::LeaveFamilyOnBehalf { member_address, - signature, + node_identity_signature, family_head, } => crate::families::transactions::try_leave_family_on_behalf( deps, info, member_address, - signature, + node_identity_signature, family_head, ), ExecuteMsg::KickFamilyMemberOnBehalf { @@ -189,6 +213,9 @@ pub fn execute( epoch_duration_secs, force_immediately, ), + ExecuteMsg::BeginEpochTransition {} => { + crate::interval::transactions::try_begin_epoch_transition(deps, env, info) + } ExecuteMsg::AdvanceCurrentEpoch { new_rewarded_set, // families_in_layer, @@ -369,6 +396,7 @@ pub fn query( QueryMsg::GetContractVersion {} => { to_binary(&crate::mixnet_contract_settings::queries::query_contract_version()) } + QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?), QueryMsg::GetStateParams {} => to_binary( &crate::mixnet_contract_settings::queries::query_contract_settings_params(deps)?, ), @@ -381,6 +409,9 @@ pub fn query( QueryMsg::GetRewardingParams {} => { to_binary(&crate::rewards::queries::query_rewarding_params(deps)?) } + QueryMsg::GetEpochStatus {} => { + to_binary(&crate::interval::queries::query_epoch_status(deps)?) + } QueryMsg::GetCurrentIntervalDetails {} => to_binary( &crate::interval::queries::query_current_interval_details(deps, env)?, ), @@ -547,6 +578,9 @@ pub fn query( limit, )?, ), + QueryMsg::GetNumberOfPendingEvents {} => to_binary( + &crate::interval::queries::query_number_of_pending_events(deps)?, + ), }; Ok(query_res?) @@ -558,6 +592,42 @@ pub fn migrate( _env: Env, msg: MigrateMsg, ) -> Result { + // this is the first migration that uses cw2 standard and thus the value in the storage doesn't yet exist + // set it instead. + if matches!( + cw2::get_contract_version(deps.storage), + Err(StdError::NotFound { .. }) + ) { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + crate::queued_migrations::create_epoch_status(deps.storage)?; + return Ok(Response::new()); + } + + // note: don't remove this particular bit of code as we have to ALWAYS check whether we have to update the stored version + let version: Version = CONTRACT_VERSION.parse().map_err(|error: semver::Error| { + MixnetContractError::SemVerFailure { + value: CONTRACT_VERSION.to_string(), + error_message: error.to_string(), + } + })?; + + let storage_version_raw = cw2::get_contract_version(deps.storage)?.version; + let storage_version: Version = + storage_version_raw + .parse() + .map_err(|error: semver::Error| MixnetContractError::SemVerFailure { + value: storage_version_raw, + error_message: error.to_string(), + })?; + + if storage_version < version { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + // If state structure changed in any contract version in the way migration is needed, it + // should occur here + crate::queued_migrations::create_epoch_status(deps.storage)?; + } + // due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address // and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh // environment, one of the contracts will HAVE TO go through a migration diff --git a/contracts/mixnet/src/delegations/helpers.rs b/contracts/mixnet/src/delegations/helpers.rs index b21e0d9ba4..3991711a06 100644 --- a/contracts/mixnet/src/delegations/helpers.rs +++ b/contracts/mixnet/src/delegations/helpers.rs @@ -38,10 +38,10 @@ mod tests { test.add_immediate_delegation(delegator, og_amount, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - let dist1 = test.reward_with_distribution(mix_id, performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + let dist1 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); let mix_rewarding = test.mix_rewarding(mix_id); let delegation = test.delegation(mix_id, delegator, &None); diff --git a/contracts/mixnet/src/delegations/transactions.rs b/contracts/mixnet/src/delegations/transactions.rs index 271a2c190d..ae505f6986 100644 --- a/contracts/mixnet/src/delegations/transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -5,7 +5,9 @@ use super::storage; use crate::interval::storage as interval_storage; use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::mixnodes::storage as mixnodes_storage; -use crate::support::helpers::validate_delegation_stake; +use crate::support::helpers::{ + ensure_epoch_in_progress_state, ensure_sent_by_vesting_contract, validate_delegation_stake, +}; use cosmwasm_std::{Addr, Coin, DepsMut, Env, MessageInfo, Response}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ @@ -30,6 +32,8 @@ pub(crate) fn try_delegate_to_mixnode_on_behalf( mix_id: MixId, delegate: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let delegate = deps.api.addr_validate(&delegate)?; _try_delegate_to_mixnode(deps, env, mix_id, delegate, info.funds, Some(info.sender)) } @@ -42,6 +46,9 @@ pub(crate) fn _try_delegate_to_mixnode( amount: Vec, proxy: Option, ) -> Result { + // delegation is only allowed if the epoch is currently not in the process of being advanced + ensure_epoch_in_progress_state(deps.storage)?; + // check if the delegation contains any funds of the appropriate denomination let contract_state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?; let delegation = validate_delegation_stake( @@ -89,6 +96,8 @@ pub(crate) fn try_remove_delegation_from_mixnode_on_behalf( mix_id: MixId, delegate: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let delegate = deps.api.addr_validate(&delegate)?; _try_remove_delegation_from_mixnode(deps, env, mix_id, delegate, Some(info.sender)) } @@ -100,6 +109,9 @@ pub(crate) fn _try_remove_delegation_from_mixnode( delegate: Addr, proxy: Option, ) -> Result { + // undelegation is only allowed if the epoch is currently not in the process of being advanced + ensure_epoch_in_progress_state(deps.storage)?; + // see if the delegation even exists let storage_key = Delegation::generate_storage_key(mix_id, &delegate, proxy.as_ref()); @@ -140,6 +152,40 @@ mod tests { use crate::support::tests::test_helpers::TestSetup; use cosmwasm_std::testing::mock_info; use cosmwasm_std::{coin, Decimal}; + use mixnet_contract_common::{EpochState, EpochStatus}; + + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + let env = test.env(); + + let owner = "delegator"; + let mix_id = test.add_dummy_mixnode("mix-owner", None); + let sender = mock_info(owner, &[coin(50_000_000, TEST_COIN_DENOM)]); + + let res = try_delegate_to_mixnode(test.deps_mut(), env.clone(), sender, mix_id); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + } #[test] fn can_only_be_done_towards_an_existing_mixnode() { @@ -353,6 +399,35 @@ mod tests { } ); } + + #[test] + fn fails_for_illegal_proxy() { + let mut test = TestSetup::new(); + let env = test.env(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let owner = "delegator"; + let mix_id = test.add_dummy_mixnode("mix-owner", None); + + let res = try_delegate_to_mixnode_on_behalf( + test.deps_mut(), + env, + mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), + mix_id, + owner.into(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } } #[cfg(test)] @@ -363,6 +438,42 @@ mod tests { use crate::support::tests::test_helpers::TestSetup; use cosmwasm_std::coin; use cosmwasm_std::testing::mock_info; + use mixnet_contract_common::{EpochState, EpochStatus}; + + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let mix_id = test.add_dummy_mixnode("owner", None); + test.add_immediate_delegation("foomp", 1000u32, mix_id); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + let env = test.env(); + let res = try_remove_delegation_from_mixnode( + test.deps_mut(), + env.clone(), + mock_info("sender", &[]), + mix_id, + ); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + } #[test] fn cannot_be_performed_if_delegation_never_existed() { @@ -462,5 +573,40 @@ mod tests { ); assert!(res.is_ok()); } + + #[test] + fn fails_for_illegal_proxy() { + let mut test = TestSetup::new(); + let env = test.env(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let owner = "delegator"; + let mix_id = test.add_dummy_mixnode("mix-owner", None); + test.add_immediate_delegation_with_illegal_proxy( + owner, + 10000u32, + mix_id, + illegal_proxy.clone(), + ); + + let res = try_remove_delegation_from_mixnode_on_behalf( + test.deps_mut(), + env, + mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), + mix_id, + owner.into(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } } } diff --git a/contracts/mixnet/src/families/storage.rs b/contracts/mixnet/src/families/storage.rs index 2a99f9054e..a60df7a87d 100644 --- a/contracts/mixnet/src/families/storage.rs +++ b/contracts/mixnet/src/families/storage.rs @@ -81,14 +81,13 @@ pub fn remove_family_member(store: &mut dyn Storage, member: IdentityKeyRef<'_>) MEMBERS.remove(store, member.to_string()) } -#[allow(dead_code)] pub fn is_family_member( store: &dyn Storage, f: &Family, member: IdentityKeyRef<'_>, ) -> Result { - let m = get_members(f, store)?; - Ok(m.contains(member)) + let existing_head = MEMBERS.may_load(store, member.to_owned())?; + Ok(existing_head.as_ref() == Some(f.head())) } pub fn is_any_member( diff --git a/contracts/mixnet/src/families/transactions.rs b/contracts/mixnet/src/families/transactions.rs index 7f9412f1f2..e392b62dea 100644 --- a/contracts/mixnet/src/families/transactions.rs +++ b/contracts/mixnet/src/families/transactions.rs @@ -1,5 +1,6 @@ use crate::support::helpers::{ - ensure_bonded, validate_family_signature, validate_node_identity_signature, + ensure_bonded, ensure_sent_by_vesting_contract, validate_family_signature, + validate_node_identity_signature, }; use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response}; @@ -28,6 +29,8 @@ pub fn try_create_family_on_behalf( owner_signature: String, label: &str, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let owner_address = deps.api.addr_validate(&owner_address)?; _try_create_family( deps, @@ -77,29 +80,47 @@ fn _try_create_family( pub fn try_join_family( deps: DepsMut, info: MessageInfo, - signature: String, + // Required for proxy joining + node_identity_signature: Option, + family_signature: String, family_head: IdentityKey, ) -> Result { let family_head = FamilyHead::new(&family_head); - _try_join_family(deps, &info.sender, signature, family_head) + _try_join_family( + deps, + &info.sender, + node_identity_signature, + family_signature, + family_head, + ) } pub fn try_join_family_on_behalf( deps: DepsMut, - _info: MessageInfo, + info: MessageInfo, member_address: String, - signature: String, + node_identity_signature: Option, + family_signature: String, family_head: IdentityKey, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let member_address = deps.api.addr_validate(&member_address)?; let family_head = FamilyHead::new(&family_head); - _try_join_family(deps, &member_address, signature, family_head) + _try_join_family( + deps, + &member_address, + node_identity_signature, + family_signature, + family_head, + ) } fn _try_join_family( deps: DepsMut, owner: &Addr, - signature: String, + node_identity_signature: Option, + family_signature: String, family_head: FamilyHead, ) -> Result { let existing_bond = crate::mixnodes::storage::mixnode_bonds() @@ -126,10 +147,19 @@ fn _try_join_family( )); } + if let Some(node_identity_signature) = node_identity_signature { + validate_node_identity_signature( + deps.as_ref(), + owner, + &node_identity_signature, + existing_bond.identity(), + )?; + } + validate_family_signature( deps.as_ref(), existing_bond.identity(), - &signature, + &family_signature, family_head.identity(), )?; @@ -152,20 +182,22 @@ pub fn try_leave_family( pub fn try_leave_family_on_behalf( deps: DepsMut, - _info: MessageInfo, + info: MessageInfo, member_address: String, - signature: String, + node_family_signature: String, family_head: IdentityKey, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let family_head = FamilyHead::new(&family_head); let member_address = deps.api.addr_validate(&member_address)?; - _try_leave_family(deps, &member_address, signature, family_head) + _try_leave_family(deps, &member_address, node_family_signature, family_head) } fn _try_leave_family( deps: DepsMut, owner: &Addr, - signature: String, + node_family_signature: String, family_head: FamilyHead, ) -> Result { let existing_bond = crate::mixnodes::storage::mixnode_bonds() @@ -194,11 +226,11 @@ fn _try_leave_family( }); } - validate_family_signature( + validate_node_identity_signature( deps.as_ref(), + owner, + &node_family_signature, existing_bond.identity(), - &signature, - family_head.identity(), )?; remove_family_member(deps.storage, existing_bond.identity()); @@ -217,11 +249,13 @@ pub fn try_head_kick_member( pub fn try_head_kick_member_on_behalf( deps: DepsMut, - _info: MessageInfo, + info: MessageInfo, head_address: String, owner_signature: String, member: IdentityKeyRef, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let head_address = deps.api.addr_validate(&head_address)?; _try_head_kick_member(deps, &head_address, owner_signature, member) } @@ -312,7 +346,7 @@ mod test { deps.as_mut(), env.clone(), mock_info(malicious_head, &[minimum_pledge.clone()]), - malicious_mixnode.clone(), + malicious_mixnode, cost_params.clone(), malicious_sig.clone(), ) @@ -320,28 +354,22 @@ mod test { crate::mixnodes::transactions::try_add_mixnode( deps.as_mut(), - env.clone(), - mock_info(member, &[minimum_pledge.clone()]), + env, + mock_info(member, &[minimum_pledge]), member_mixnode.clone(), - cost_params.clone(), + cost_params, member_sig.clone(), ) .unwrap(); - try_create_family( - deps.as_mut(), - mock_info(head.clone(), &[]), - head_sig.clone(), - "test", - ) - .unwrap(); + try_create_family(deps.as_mut(), mock_info(head, &[]), head_sig, "test").unwrap(); let family_head = FamilyHead::new(&head_mixnode.identity_key); assert!(get_family(&family_head, &deps.storage).is_ok()); let nope = try_create_family( deps.as_mut(), - mock_info(malicious_head.clone(), &[]), - malicious_sig.clone(), + mock_info(malicious_head, &[]), + malicious_sig, "test", ); @@ -368,6 +396,7 @@ mod test { try_join_family( deps.as_mut(), mock_info(member, &[]), + Some(member_sig.clone()), join_signature.clone(), head_mixnode.identity_key.clone(), ) @@ -380,7 +409,7 @@ mod test { try_leave_family( deps.as_mut(), mock_info(member, &[]), - join_signature.clone(), + member_sig.clone(), head_mixnode.identity_key.clone(), ) .unwrap(); @@ -391,6 +420,7 @@ mod test { try_join_family( deps.as_mut(), mock_info(member, &[]), + Some(member_sig.clone()), join_signature.clone(), head_mixnode.identity_key.clone(), ) @@ -411,4 +441,201 @@ mod test { // let family = get_family(&family_head, &deps.storage).unwrap(); // assert!(!is_family_member(&deps.storage, &family, &member_mixnode.identity_key).unwrap()); } + + #[cfg(test)] + mod creating_family { + use super::*; + use crate::support::tests::test_helpers::TestSetup; + + #[test] + fn fails_for_illegal_proxy() { + let mut test = TestSetup::new(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let head = "alice"; + + let (_, keypair) = test.add_dummy_mixnode_with_keypair(head, None); + let sig = keypair.private_key().sign_text(head); + + let res = try_create_family_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &[]), + head.to_string(), + sig, + "label", + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + } + + #[cfg(test)] + mod joining_family { + use super::*; + use crate::support::tests::test_helpers::TestSetup; + + #[test] + fn fails_for_illegal_proxy() { + let mut test = TestSetup::new(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let head = "alice"; + let label = "family"; + let new_member = "vin-diesel"; + + let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label); + let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None); + + // TODO: those signatures are WRONG and have to be c hanged + let join_signature = head_keys + .private_key() + .sign_text(&member_keys.public_key().to_base58_string()); + + let member_sig = member_keys.private_key().sign_text(new_member); + + let head_identity = head_keys.public_key().to_base58_string(); + let res = try_join_family_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &[]), + new_member.to_string(), + Some(member_sig), + join_signature, + head_identity, + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + } + + #[cfg(test)] + mod leaving_family { + use super::*; + use crate::support::tests::test_helpers::TestSetup; + + #[test] + fn fails_for_illegal_proxy() { + let mut test = TestSetup::new(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let head = "alice"; + let label = "family"; + let new_member = "vin-diesel"; + + let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label); + let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None); + + // TODO: those signatures are WRONG and have to be changed + let join_signature = head_keys + .private_key() + .sign_text(&member_keys.public_key().to_base58_string()); + + let member_sig = member_keys.private_key().sign_text(new_member); + + let head_identity = head_keys.public_key().to_base58_string(); + try_join_family_on_behalf( + test.deps_mut(), + mock_info(vesting_contract.as_ref(), &[]), + new_member.to_string(), + Some(member_sig.clone()), + join_signature, + head_identity, + ) + .unwrap(); + + let leave_signature = member_sig; + let res = try_leave_family_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &[]), + new_member.to_string(), + leave_signature, + head_keys.public_key().to_base58_string(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + } + + #[cfg(test)] + mod kicking_family_member { + use super::*; + use crate::support::tests::test_helpers::TestSetup; + + #[test] + fn fails_for_illegal_proxy() { + let mut test = TestSetup::new(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let head = "alice"; + let label = "family"; + let new_member = "vin-diesel"; + + let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label); + let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None); + + // TODO: those signatures are WRONG and have to be c hanged + let join_signature = head_keys + .private_key() + .sign_text(&member_keys.public_key().to_base58_string()); + + let member_sig = member_keys.private_key().sign_text(new_member); + + let head_identity = head_keys.public_key().to_base58_string(); + try_join_family_on_behalf( + test.deps_mut(), + mock_info(vesting_contract.as_ref(), &[]), + new_member.to_string(), + Some(member_sig.clone()), + join_signature, + head_identity, + ) + .unwrap(); + + // TODO: a completely wrong signature is being used + let res = try_head_kick_member_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &[]), + head.to_string(), + head_keys.private_key().sign_text(head), + &member_keys.public_key().to_base58_string(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + } } diff --git a/contracts/mixnet/src/gateways/transactions.rs b/contracts/mixnet/src/gateways/transactions.rs index dc53817987..2078b0a180 100644 --- a/contracts/mixnet/src/gateways/transactions.rs +++ b/contracts/mixnet/src/gateways/transactions.rs @@ -4,7 +4,8 @@ use super::storage; use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::support::helpers::{ - ensure_no_existing_bond, validate_node_identity_signature, validate_pledge, + ensure_no_existing_bond, ensure_sent_by_vesting_contract, validate_node_identity_signature, + validate_pledge, }; use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response}; use mixnet_contract_common::error::MixnetContractError; @@ -38,6 +39,8 @@ pub fn try_add_gateway_on_behalf( owner: String, owner_signature: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let proxy = info.sender; let owner = deps.api.addr_validate(&owner)?; _try_add_gateway( @@ -65,7 +68,7 @@ pub(crate) fn _try_add_gateway( let pledge = validate_pledge(pledge, minimum_pledge)?; // if the client has an active bonded mixnode or gateway, don't allow bonding - ensure_no_existing_bond(deps.storage, &owner)?; + ensure_no_existing_bond(&owner, deps.storage)?; // check if somebody else has already bonded a gateway with this identity if let Some(existing_bond) = @@ -110,6 +113,8 @@ pub fn try_remove_gateway_on_behalf( info: MessageInfo, owner: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let proxy = info.sender; let owner = deps.api.addr_validate(&owner)?; _try_remove_gateway(deps, owner, Some(proxy)) @@ -178,11 +183,14 @@ pub(crate) fn _try_remove_gateway( #[cfg(test)] pub mod tests { use crate::contract::execute; - use crate::gateways::transactions::try_add_gateway; + use crate::gateways::transactions::{ + try_add_gateway, try_add_gateway_on_behalf, try_remove_gateway_on_behalf, + }; use crate::interval::pending_events; use crate::mixnet_contract_settings::storage::minimum_gateway_pledge; use crate::support::tests; - use crate::support::tests::fixtures::TEST_COIN_DENOM; + use crate::support::tests::fixtures::{good_gateway_pledge, TEST_COIN_DENOM}; + use crate::support::tests::test_helpers::TestSetup; use crate::support::tests::{fixtures, test_helpers}; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coin, Addr, BankMsg, Response, Uint128}; @@ -280,6 +288,36 @@ pub mod tests { assert!(result.is_ok()); } + #[test] + fn gateway_add_with_illegal_proxy() { + let mut test = TestSetup::new(); + let env = test.env(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let owner = "alice"; + let (gateway, sig) = test_helpers::gateway_with_signature(&mut test.rng, owner); + + let res = try_add_gateway_on_behalf( + test.deps_mut(), + env, + mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()), + gateway, + owner.to_string(), + sig, + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + #[test] fn gateway_remove() { let mut deps = test_helpers::init_contract(); @@ -367,4 +405,31 @@ pub mod tests { assert_eq!(1, gateway_bonds.len()); assert_eq!(&Addr::unchecked("bob"), gateway_bonds[0].owner()); } + + #[test] + fn gateway_remove_with_illegal_proxy() { + let mut test = TestSetup::new(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let owner = "alice"; + + test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone()); + + let res = try_remove_gateway_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()), + owner.to_string(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } } diff --git a/contracts/mixnet/src/interval/pending_events.rs b/contracts/mixnet/src/interval/pending_events.rs index b242944988..323107d71e 100644 --- a/contracts/mixnet/src/interval/pending_events.rs +++ b/contracts/mixnet/src/interval/pending_events.rs @@ -5,12 +5,11 @@ use crate::delegations; use crate::delegations::storage as delegations_storage; use crate::interval::helpers::change_interval_config; use crate::interval::storage; -use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::mixnodes::helpers::{cleanup_post_unbond_mixnode_storage, get_mixnode_details_by_id}; use crate::mixnodes::storage as mixnodes_storage; use crate::rewards::storage as rewards_storage; -use crate::support::helpers::send_to_proxy_or_owner; -use cosmwasm_std::{wasm_execute, Addr, Coin, DepsMut, Env, Response}; +use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking}; +use cosmwasm_std::{Addr, Coin, DepsMut, Env, Response}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ new_active_set_update_event, new_delegation_event, new_delegation_on_unbonded_node_event, @@ -24,7 +23,6 @@ use mixnet_contract_common::pending_events::{ }; use mixnet_contract_common::reward_params::IntervalRewardingParamsUpdate; use mixnet_contract_common::{BlockHeight, Delegation, MixId}; -use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; pub(crate) trait ContractExecutableEvent { // note: the error only means a HARD error like we failed to read from storage. @@ -56,27 +54,20 @@ pub(crate) fn delegate( _ => { // if mixnode is no longer bonded or in the process of unbonding, return the tokens back to the // delegator; + // (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`) let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![amount.clone()]); - let mut response = Response::new().add_message(return_tokens).add_event( - new_delegation_on_unbonded_node_event(&owner, &proxy, mix_id), - ); - - if let Some(proxy) = &proxy { - // we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract - // otherwise, we don't care - let vesting_contract = - mixnet_params_storage::vesting_contract_address(deps.storage)?; - if proxy == &vesting_contract { - let msg = VestingContractExecuteMsg::TrackUndelegation { - owner: owner.into_string(), - mix_id, - amount, - }; - - let track_undelegate_message = wasm_execute(proxy, &msg, vec![])?; - response = response.add_message(track_undelegate_message); - } - } + let response = Response::new() + .add_message(return_tokens) + .add_event(new_delegation_on_unbonded_node_event( + &owner, &proxy, mix_id, + )) + .maybe_add_track_vesting_undelegation_message( + deps.storage, + proxy, + owner.to_string(), + mix_id, + amount, + )?; return Ok(response); } @@ -163,26 +154,18 @@ pub(crate) fn undelegate( let tokens_to_return = delegations::helpers::undelegate(deps.storage, delegation, mix_rewarding)?; + // (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`) let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![tokens_to_return.clone()]); - let mut response = Response::new() + let response = Response::new() .add_message(return_tokens) - .add_event(new_undelegation_event(created_at, &owner, &proxy, mix_id)); - - if let Some(proxy) = &proxy { - // we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract - // otherwise, we don't care - let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?; - if proxy == &vesting_contract { - let msg = VestingContractExecuteMsg::TrackUndelegation { - owner: owner.into_string(), - mix_id, - amount: tokens_to_return, - }; - - let track_undelegate_message = wasm_execute(proxy, &msg, vec![])?; - response = response.add_message(track_undelegate_message); - } - } + .add_event(new_undelegation_event(created_at, &owner, &proxy, mix_id)) + .maybe_add_track_vesting_undelegation_message( + deps.storage, + proxy, + owner.to_string(), + mix_id, + tokens_to_return, + )?; Ok(response) } @@ -219,24 +202,15 @@ pub(crate) fn unbond_mixnode( // decrement the associated layer count cleanup_post_unbond_mixnode_storage(deps.storage, env, &node_details)?; - let mut response = Response::new() + let response = Response::new() .add_message(return_tokens) - .add_event(new_mixnode_unbonding_event(created_at, mix_id)); - - if let Some(proxy) = &proxy { - // we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract - // otherwise, we don't care - let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?; - if proxy == &vesting_contract { - let msg = VestingContractExecuteMsg::TrackUnbondMixnode { - owner: owner.clone().into_string(), - amount: tokens, - }; - - let track_unbond_message = wasm_execute(proxy, &msg, vec![])?; - response = response.add_message(track_unbond_message); - } - } + .add_event(new_mixnode_unbonding_event(created_at, mix_id)) + .maybe_add_track_vesting_unbond_mixnode_message( + deps.storage, + proxy.clone(), + owner.clone().into_string(), + tokens, + )?; Ok(response) } @@ -429,6 +403,7 @@ mod tests { use cosmwasm_std::Decimal; use mixnet_contract_common::Percent; use std::time::Duration; + use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; // note that authorization and basic validation has already been performed for all of those // before being pushed onto the event queues @@ -628,19 +603,31 @@ mod tests { let delegation_coin_new = coin(delegation_new, TEST_COIN_DENOM); // perform some rewarding here to advance the unit delegation beyond the initial value - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let owner = "delegator"; test.add_immediate_delegation(owner, delegation_og, mix_id); test.skip_to_next_epoch_end(); - let dist1 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let storage_key = Delegation::generate_storage_key(mix_id, &Addr::unchecked(owner), None); @@ -699,11 +686,17 @@ mod tests { let delegation_coin = coin(120_000_000u128, TEST_COIN_DENOM); // perform some rewarding here to advance the unit delegation beyond the initial value - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let storage_key = Delegation::generate_storage_key(mix_id, &Addr::unchecked(owner), None); @@ -749,21 +742,19 @@ mod tests { let delegation = 120_000_000u128; let delegation_coin = coin(delegation, TEST_COIN_DENOM); - let owner1 = "delegator1"; - let owner2 = "delegator2"; + let owner = "delegator"; let env = test.env(); unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap(); let vesting_contract = test.vesting_contract(); - let dummy_proxy = Addr::unchecked("not-vesting-contract"); // for a fresh delegation, nothing was added to the storage either let res_vesting = delegate( test.deps_mut(), &env, 123, - Addr::unchecked(owner1), + Addr::unchecked(owner), mix_id, delegation_coin.clone(), Some(vesting_contract.clone()), @@ -771,7 +762,7 @@ mod tests { .unwrap(); let storage_key = Delegation::generate_storage_key( mix_id, - &Addr::unchecked(owner1), + &Addr::unchecked(owner), Some(vesting_contract.clone()).as_ref(), ); assert!(delegations_storage::delegations() @@ -795,7 +786,7 @@ mod tests { found_track = true; assert_eq!(contract_addr, vesting_contract.as_str()); let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation { - owner: owner1.to_string(), + owner: owner.to_string(), mix_id, amount: delegation_coin.clone(), }) @@ -805,37 +796,42 @@ mod tests { } } assert!(found_track); + } + #[test] + fn returns_error_for_illegal_proxy() { + let mut test = TestSetup::new(); + let mix_id = test.add_dummy_mixnode("mix-owner", None); + + let delegation = 120_000_000u128; + let delegation_coin = coin(delegation, TEST_COIN_DENOM); + let owner = "delegator"; + let dummy_proxy = Addr::unchecked("not-vesting-contract"); + + let env = test.env(); + unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap(); + + let vesting_contract = test.vesting_contract(); + + // try to add illegal delegation (with invalid proxy) let res_other_proxy = delegate( test.deps_mut(), &env, 123, - Addr::unchecked(owner1), + Addr::unchecked(owner), mix_id, - delegation_coin.clone(), + delegation_coin, Some(dummy_proxy.clone()), ) - .unwrap(); - let storage_key = Delegation::generate_storage_key( - mix_id, - &Addr::unchecked(owner2), - Some(dummy_proxy.clone()).as_ref(), - ); - assert!(delegations_storage::delegations() - .may_load(test.deps().storage, storage_key) - .unwrap() - .is_none()); - // and all tokens are returned back to the proxy - let (receiver, sent_amount) = get_bank_send_msg(&res_other_proxy).unwrap(); - assert_eq!(receiver, dummy_proxy.as_str()); - assert_eq!(sent_amount[0], delegation_coin); + .unwrap_err(); - // no track messages here! - for msg in &res_other_proxy.messages { - if let CosmosMsg::Wasm(WasmMsg::Execute { .. }) = &msg.msg { - panic!("we shouldn't have attempted to call any other contract!") + assert_eq!( + res_other_proxy, + MixnetContractError::ProxyIsNotVestingContract { + received: dummy_proxy, + vesting_contract } - } + ); } } @@ -885,18 +881,30 @@ mod tests { let delegation = 120_000_000u128; // perform some rewarding here to advance the unit delegation beyond the initial value - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.add_immediate_delegation(owner, delegation, mix_id); test.skip_to_next_epoch_end(); - let dist1 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let expected_reward = dist1.delegates + dist2.delegates; let truncated_reward = truncate_reward_amount(expected_reward); @@ -930,37 +938,23 @@ mod tests { let delegation = 120_000_000u128; let delegation_coin = coin(delegation, TEST_COIN_DENOM); - let owner1 = "delegator1"; - let owner2 = "delegator2"; + let owner = "delegator"; let vesting_contract = test.vesting_contract(); - let dummy_proxy = Addr::unchecked("not-vesting-contract"); - test.add_immediate_delegation_with_proxy( - owner1, - delegation, - mix_id, - vesting_contract.clone(), - ); - test.add_immediate_delegation_with_proxy( - owner2, - delegation, - mix_id, - dummy_proxy.clone(), - ); + test.add_immediate_delegation_with_legal_proxy(owner, delegation, mix_id); - // for a fresh delegation, nothing was added to the storage either let res_vesting = undelegate( test.deps_mut(), 123, - Addr::unchecked(owner1), + Addr::unchecked(owner), mix_id, Some(vesting_contract.clone()), ) .unwrap(); let storage_key = Delegation::generate_storage_key( mix_id, - &Addr::unchecked(owner1), + &Addr::unchecked(owner), Some(vesting_contract.clone()).as_ref(), ); assert!(delegations_storage::delegations() @@ -985,7 +979,7 @@ mod tests { found_track = true; assert_eq!(contract_addr, vesting_contract.as_str()); let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation { - owner: owner1.to_string(), + owner: owner.to_string(), mix_id, amount: delegation_coin.clone(), }) @@ -995,35 +989,41 @@ mod tests { } } assert!(found_track); + } + + #[test] + fn returns_error_for_illegal_proxy() { + let mut test = TestSetup::new(); + let mix_id = test.add_dummy_mixnode("mix-owner", None); + + let delegation = 120_000_000u128; + let owner = "delegator1"; + + let vesting_contract = test.vesting_contract(); + let dummy_proxy = Addr::unchecked("not-vesting-contract"); + + test.add_immediate_delegation_with_illegal_proxy( + owner, + delegation, + mix_id, + dummy_proxy.clone(), + ); let res_other_proxy = undelegate( test.deps_mut(), 123, - Addr::unchecked(owner2), + Addr::unchecked(owner), mix_id, Some(dummy_proxy.clone()), ) - .unwrap(); - let storage_key = Delegation::generate_storage_key( - mix_id, - &Addr::unchecked(owner2), - Some(dummy_proxy.clone()).as_ref(), - ); - assert!(delegations_storage::delegations() - .may_load(test.deps().storage, storage_key) - .unwrap() - .is_none()); - // and all tokens are returned back to the proxy - let (receiver, sent_amount) = get_bank_send_msg(&res_other_proxy).unwrap(); - assert_eq!(receiver, dummy_proxy.as_str()); - assert_eq!(sent_amount[0], delegation_coin); - - // no track messages here! - for msg in &res_other_proxy.messages { - if let CosmosMsg::Wasm(WasmMsg::Execute { .. }) = &msg.msg { - panic!("we shouldn't have attempted to call any other contract!") + .unwrap_err(); + assert_eq!( + res_other_proxy, + MixnetContractError::ProxyIsNotVestingContract { + received: dummy_proxy, + vesting_contract } - } + ); } } @@ -1062,11 +1062,17 @@ mod tests { .unwrap(); let layer = mix_details.layer; - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); - let dist1 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let expected_reward = dist1.operator + dist2.operator; let truncated_reward = truncate_reward_amount(expected_reward); @@ -1109,19 +1115,14 @@ mod tests { let mut test = TestSetup::new(); let vesting_contract = test.vesting_contract(); - let dummy_proxy = Addr::unchecked("not-vesting-contract"); let pledge = Uint128::new(250_000_000); let pledge_coin = coin(250_000_000, TEST_COIN_DENOM); - let owner1 = "mix-owner1"; - let owner2 = "mix-owner2"; - let mix_id_vesting = - test.add_dummy_mixnode_with_proxy(owner1, Some(pledge), vesting_contract.clone()); - let mix_id_other_proxy = - test.add_dummy_mixnode_with_proxy(owner2, Some(pledge), dummy_proxy.clone()); + let owner = "mix-owner1"; + let mix_id_vesting = test.add_dummy_mixnode_with_legal_proxy(owner, Some(pledge)); let env = test.env(); - let res_vesting = unbond_mixnode(test.deps_mut(), &env, 123, mix_id_vesting).unwrap(); + let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id_vesting).unwrap(); assert!(mixnodes_storage::mixnode_bonds() .may_load(test.deps().storage, mix_id_vesting) @@ -1129,13 +1130,13 @@ mod tests { .is_none()); // and all tokens are returned back to the proxy - let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap(); + let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap(); assert_eq!(receiver, vesting_contract.as_str()); assert_eq!(sent_amount[0], pledge_coin); // and we get appropriate track message let mut found_track = true; - for msg in &res_vesting.messages { + for msg in &res.messages { if let CosmosMsg::Wasm(WasmMsg::Execute { contract_addr, msg, @@ -1145,7 +1146,7 @@ mod tests { found_track = true; assert_eq!(contract_addr, vesting_contract.as_str()); let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUnbondMixnode { - owner: owner1.to_string(), + owner: owner.to_string(), amount: pledge_coin.clone(), }) .unwrap(); @@ -1154,24 +1155,32 @@ mod tests { } } assert!(found_track); + } + #[test] + fn returns_error_for_illegal_proxy() { + let mut test = TestSetup::new(); + + let dummy_proxy = Addr::unchecked("not-vesting-contract"); + let env = test.env(); + + let vesting_contract = test.vesting_contract(); + let owner = "mix-owner"; + let pledge = Uint128::new(250_000_000); + + let mix_id_illegal_proxy = + test.add_dummy_mixnode_with_illegal_proxy(owner, Some(pledge), dummy_proxy.clone()); + + // this is the halting issue that should have never occurred let res_other_proxy = - unbond_mixnode(test.deps_mut(), &env, 123, mix_id_other_proxy).unwrap(); - assert!(mixnodes_storage::mixnode_bonds() - .may_load(test.deps().storage, mix_id_other_proxy) - .unwrap() - .is_none()); - // and all tokens are returned back to the proxy - let (receiver, sent_amount) = get_bank_send_msg(&res_other_proxy).unwrap(); - assert_eq!(receiver, dummy_proxy.as_str()); - assert_eq!(sent_amount[0], pledge_coin); - - // no track messages here! - for msg in &res_other_proxy.messages { - if let CosmosMsg::Wasm(WasmMsg::Execute { .. }) = &msg.msg { - panic!("we shouldn't have attempted to call any other contract!") + unbond_mixnode(test.deps_mut(), &env, 123, mix_id_illegal_proxy).unwrap_err(); + assert_eq!( + res_other_proxy, + MixnetContractError::ProxyIsNotVestingContract { + received: dummy_proxy, + vesting_contract } - } + ); } } @@ -1244,12 +1253,16 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111u128, mix_id_full_pledge); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); - let dist1 = - test.reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0)); - let dist2 = - test.reward_with_distribution(mix_id_full_pledge, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id_repledge, + test_helpers::performance(100.0), + ); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id_full_pledge, + test_helpers::performance(100.0), + ); assert_eq!(dist1, dist2) } @@ -1267,10 +1280,12 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_repledge]); + test.force_change_rewarded_set(vec![mix_id_repledge]); - let dist = - test.reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id_repledge, + test_helpers::performance(100.0), + ); let increase = test.coin(pledge2.u128()); increase_pledge(test.deps_mut(), 123, mix_id_repledge, increase).unwrap(); @@ -1306,15 +1321,19 @@ mod tests { ); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); // go through few epochs of rewarding for _ in 0..500 { test.skip_to_next_epoch_end(); - let dist1 = test - .reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0)); - let dist2 = test - .reward_with_distribution(mix_id_full_pledge, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id_repledge, + test_helpers::performance(100.0), + ); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id_full_pledge, + test_helpers::performance(100.0), + ); assert_eq!(dist1, dist2) } @@ -1333,7 +1352,7 @@ mod tests { test.add_immediate_delegation("carol", 111_111_111_000u128, mix_id_repledge); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_repledge]); + test.force_change_rewarded_set(vec![mix_id_repledge]); let mut cumulative_op_reward = Decimal::zero(); let mut cumulative_del_reward = Decimal::zero(); @@ -1341,8 +1360,10 @@ mod tests { // go few epochs of rewarding before adding more pledge for _ in 0..500 { test.skip_to_next_epoch_end(); - let dist = test - .reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id_repledge, + test_helpers::performance(100.0), + ); cumulative_op_reward += dist.operator; cumulative_del_reward += dist.delegates; } @@ -1382,15 +1403,19 @@ mod tests { ); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); + test.force_change_rewarded_set(vec![mix_id_repledge, mix_id_full_pledge]); // go through few more epochs of rewarding for _ in 0..500 { test.skip_to_next_epoch_end(); - let dist1 = test - .reward_with_distribution(mix_id_repledge, test_helpers::performance(100.0)); - let dist2 = test - .reward_with_distribution(mix_id_full_pledge, test_helpers::performance(100.0)); + let dist1 = test.reward_with_distribution_with_state_bypass( + mix_id_repledge, + test_helpers::performance(100.0), + ); + let dist2 = test.reward_with_distribution_with_state_bypass( + mix_id_full_pledge, + test_helpers::performance(100.0), + ); assert_eq!(dist1, dist2) } diff --git a/contracts/mixnet/src/interval/queries.rs b/contracts/mixnet/src/interval/queries.rs index a0fe28ed28..e1f6a51a5d 100644 --- a/contracts/mixnet/src/interval/queries.rs +++ b/contracts/mixnet/src/interval/queries.rs @@ -1,4 +1,4 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::constants::{ @@ -9,12 +9,18 @@ use crate::constants::{ use crate::interval::storage; use cosmwasm_std::{Deps, Env, Order, StdResult}; use cw_storage_plus::Bound; +use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::pending_events::{PendingEpochEvent, PendingIntervalEvent}; use mixnet_contract_common::{ - CurrentIntervalResponse, EpochEventId, IntervalEventId, MixId, PagedRewardedSetResponse, - PendingEpochEventsResponse, PendingIntervalEventsResponse, + CurrentIntervalResponse, EpochEventId, EpochStatus, IntervalEventId, MixId, + NumberOfPendingEventsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse, + PendingIntervalEventsResponse, }; +pub fn query_epoch_status(deps: Deps<'_>) -> StdResult { + storage::current_epoch_status(deps.storage) +} + pub fn query_current_interval_details( deps: Deps<'_>, env: Env, @@ -106,10 +112,60 @@ pub fn query_pending_interval_events_paged( }) } +pub fn query_number_of_pending_events( + deps: Deps<'_>, +) -> Result { + let last_executed_epoch_id = storage::LAST_PROCESSED_EPOCH_EVENT.load(deps.storage)?; + let last_inserted_epoch_id = storage::EPOCH_EVENT_ID_COUNTER.load(deps.storage)?; + + let last_executed_interval_id = storage::LAST_PROCESSED_INTERVAL_EVENT.load(deps.storage)?; + let last_inserted_interval_id = storage::INTERVAL_EVENT_ID_COUNTER.load(deps.storage)?; + + Ok(NumberOfPendingEventsResponse { + epoch_events: last_inserted_epoch_id - last_executed_epoch_id, + interval_events: last_inserted_interval_id - last_executed_interval_id, + }) +} + #[cfg(test)] mod tests { use super::*; + use crate::support::tests::fixtures; use crate::support::tests::test_helpers::TestSetup; + use cosmwasm_std::Addr; + use mixnet_contract_common::{PendingEpochEventKind, PendingIntervalEventKind}; + use rand_chacha::rand_core::RngCore; + + fn push_n_dummy_epoch_actions(test: &mut TestSetup, n: usize) { + for _ in 0..n { + push_dummy_epoch_action(test) + } + } + + fn push_dummy_epoch_action(test: &mut TestSetup) { + let dummy_action = PendingEpochEventKind::Undelegate { + owner: Addr::unchecked("foomp"), + mix_id: test.rng.next_u32(), + proxy: None, + }; + let env = test.env(); + storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap(); + } + + fn push_n_dummy_interval_actions(test: &mut TestSetup, n: usize) { + for _ in 0..n { + push_dummy_interval_action(test) + } + } + + fn push_dummy_interval_action(test: &mut TestSetup) { + let dummy_action = PendingIntervalEventKind::ChangeMixCostParams { + mix_id: test.rng.next_u32(), + new_costs: fixtures::mix_node_cost_params_fixture(), + }; + let env = test.env(); + storage::push_new_interval_event(test.deps_mut().storage, &env, dummy_action).unwrap(); + } #[test] fn querying_for_current_interval_details() { @@ -151,7 +207,7 @@ mod tests { fn set_rewarded_set_to_n_nodes(test: &mut TestSetup, n: usize) { let set = (1u32..).take(n).collect::>(); - test.update_rewarded_set(set) + test.force_change_rewarded_set(set) } #[test] @@ -237,25 +293,6 @@ mod tests { #[cfg(test)] mod pending_epoch_events { use super::*; - use cosmwasm_std::Addr; - use mixnet_contract_common::pending_events::PendingEpochEventKind; - use rand_chacha::rand_core::RngCore; - - fn push_n_dummy_epoch_actions(test: &mut TestSetup, n: usize) { - for _ in 0..n { - push_dummy_epoch_action(test) - } - } - - fn push_dummy_epoch_action(test: &mut TestSetup) { - let dummy_action = PendingEpochEventKind::Undelegate { - owner: Addr::unchecked("foomp"), - mix_id: test.rng.next_u32(), - proxy: None, - }; - let env = test.env(); - storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap(); - } #[test] fn obeys_limits() { @@ -379,24 +416,6 @@ mod tests { #[cfg(test)] mod pending_interval_events { use super::*; - use crate::support::tests::fixtures; - use mixnet_contract_common::pending_events::PendingIntervalEventKind; - use rand_chacha::rand_core::RngCore; - - fn push_n_dummy_interval_actions(test: &mut TestSetup, n: usize) { - for _ in 0..n { - push_dummy_interval_action(test) - } - } - - fn push_dummy_interval_action(test: &mut TestSetup) { - let dummy_action = PendingIntervalEventKind::ChangeMixCostParams { - mix_id: test.rng.next_u32(), - new_costs: fixtures::mix_node_cost_params_fixture(), - }; - let env = test.env(); - storage::push_new_interval_event(test.deps_mut().storage, &env, dummy_action).unwrap(); - } #[test] fn obeys_limits() { @@ -520,4 +539,59 @@ mod tests { ) } } + + #[test] + fn querying_for_number_of_pending_events() { + let mut test = TestSetup::new(); + + // no events + assert_eq!( + Ok(NumberOfPendingEventsResponse { + epoch_events: 0, + interval_events: 0 + }), + query_number_of_pending_events(test.deps()) + ); + + // add epoch event + push_dummy_epoch_action(&mut test); + assert_eq!( + Ok(NumberOfPendingEventsResponse { + epoch_events: 1, + interval_events: 0 + }), + query_number_of_pending_events(test.deps()) + ); + + // add more epoch events + push_n_dummy_epoch_actions(&mut test, 42); + assert_eq!( + Ok(NumberOfPendingEventsResponse { + epoch_events: 43, + interval_events: 0 + }), + query_number_of_pending_events(test.deps()) + ); + + // and now for interval... + // add interval event + push_dummy_interval_action(&mut test); + assert_eq!( + Ok(NumberOfPendingEventsResponse { + epoch_events: 43, + interval_events: 1 + }), + query_number_of_pending_events(test.deps()) + ); + + // add more epoch events + push_n_dummy_interval_actions(&mut test, 42); + assert_eq!( + Ok(NumberOfPendingEventsResponse { + epoch_events: 43, + interval_events: 43 + }), + query_number_of_pending_events(test.deps()) + ); + } } diff --git a/contracts/mixnet/src/interval/storage.rs b/contracts/mixnet/src/interval/storage.rs index 282876f5f8..4de46d47d2 100644 --- a/contracts/mixnet/src/interval/storage.rs +++ b/contracts/mixnet/src/interval/storage.rs @@ -1,21 +1,23 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::constants::{ - CURRENT_INTERVAL_KEY, EPOCH_EVENT_ID_COUNTER_KEY, INTERVAL_EVENT_ID_COUNTER_KEY, - LAST_EPOCH_EVENT_ID_KEY, LAST_INTERVAL_EVENT_ID_KEY, PENDING_EPOCH_EVENTS_NAMESPACE, - PENDING_INTERVAL_EVENTS_NAMESPACE, REWARDED_SET_KEY, + CURRENT_EPOCH_STATUS_KEY, CURRENT_INTERVAL_KEY, EPOCH_EVENT_ID_COUNTER_KEY, + INTERVAL_EVENT_ID_COUNTER_KEY, LAST_EPOCH_EVENT_ID_KEY, LAST_INTERVAL_EVENT_ID_KEY, + PENDING_EPOCH_EVENTS_NAMESPACE, PENDING_INTERVAL_EVENTS_NAMESPACE, REWARDED_SET_KEY, }; -use cosmwasm_std::{Env, Order, StdResult, Storage}; +use cosmwasm_std::{Addr, Env, Order, StdResult, Storage}; use cw_storage_plus::{Item, Map}; use mixnet_contract_common::pending_events::{ PendingEpochEventData, PendingEpochEventKind, PendingIntervalEventData, }; use mixnet_contract_common::{ - EpochEventId, Interval, IntervalEventId, MixId, PendingIntervalEventKind, RewardedSetNodeStatus, + EpochEventId, EpochStatus, Interval, IntervalEventId, MixId, PendingIntervalEventKind, + RewardedSetNodeStatus, }; use std::collections::HashMap; +pub(crate) const CURRENT_EPOCH_STATUS: Item<'_, EpochStatus> = Item::new(CURRENT_EPOCH_STATUS_KEY); pub(crate) const CURRENT_INTERVAL: Item<'_, Interval> = Item::new(CURRENT_INTERVAL_KEY); pub(crate) const REWARDED_SET: Map = Map::new(REWARDED_SET_KEY); @@ -39,6 +41,17 @@ pub(crate) const PENDING_EPOCH_EVENTS: Map pub(crate) const PENDING_INTERVAL_EVENTS: Map = Map::new(PENDING_INTERVAL_EVENTS_NAMESPACE); +pub(crate) fn current_epoch_status(storage: &dyn Storage) -> StdResult { + CURRENT_EPOCH_STATUS.load(storage) +} + +pub(crate) fn save_current_epoch_status( + storage: &mut dyn Storage, + status: &EpochStatus, +) -> StdResult<()> { + CURRENT_EPOCH_STATUS.save(storage, status) +} + pub(crate) fn current_interval(storage: &dyn Storage) -> StdResult { CURRENT_INTERVAL.load(storage) } @@ -69,6 +82,12 @@ pub(crate) fn push_new_epoch_event( env: &Env, event: PendingEpochEventKind, ) -> StdResult<()> { + // not included in non-test code as it messes with our return types as we expected `StdResult` + // from all storage-related operations. + // However, the callers MUST HAVE ensured the below invariant + #[cfg(test)] + crate::support::helpers::ensure_epoch_in_progress_state(storage).unwrap(); + let event_id = next_epoch_event_id_counter(storage)?; let event_data = event.attach_source_height(env.block.height); PENDING_EPOCH_EVENTS.save(storage, event_id, &event_data) @@ -79,6 +98,12 @@ pub(crate) fn push_new_interval_event( env: &Env, event: PendingIntervalEventKind, ) -> StdResult<()> { + // not included in non-test code as it messes with our return types as we expected `StdResult` + // from all storage-related operations. + // However, the callers MUST HAVE ensured the below invariant + #[cfg(test)] + crate::support::helpers::ensure_epoch_in_progress_state(storage).unwrap(); + let event_id = next_interval_event_id_counter(storage)?; let event_data = event.attach_source_height(env.block.height); PENDING_INTERVAL_EVENTS.save(storage, event_id, &event_data) @@ -130,12 +155,14 @@ pub(crate) fn update_rewarded_set( pub(crate) fn initialise_storage( storage: &mut dyn Storage, starting_interval: Interval, + rewarding_validator: Addr, ) -> StdResult<()> { save_interval(storage, &starting_interval)?; LAST_PROCESSED_EPOCH_EVENT.save(storage, &0)?; LAST_PROCESSED_INTERVAL_EVENT.save(storage, &0)?; EPOCH_EVENT_ID_COUNTER.save(storage, &0)?; - INTERVAL_EVENT_ID_COUNTER.save(storage, &0) + INTERVAL_EVENT_ID_COUNTER.save(storage, &0)?; + CURRENT_EPOCH_STATUS.save(storage, &EpochStatus::new(rewarding_validator)) } #[cfg(test)] diff --git a/contracts/mixnet/src/interval/transactions.rs b/contracts/mixnet/src/interval/transactions.rs index a55978e4e4..0392368389 100644 --- a/contracts/mixnet/src/interval/transactions.rs +++ b/contracts/mixnet/src/interval/transactions.rs @@ -1,4 +1,4 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use super::storage; @@ -8,16 +8,18 @@ use crate::interval::storage::push_new_interval_event; use crate::mixnodes::transactions::update_mixnode_layer; use crate::rewards; use crate::rewards::storage as rewards_storage; -use crate::support::helpers::{ensure_is_authorized, ensure_is_owner}; -use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, Storage}; +use crate::support::helpers::{ + ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_is_owner, +}; +use cosmwasm_std::{DepsMut, Env, MessageInfo, Order, Response, Storage}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ - new_advance_epoch_event, new_pending_epoch_events_execution_event, - new_pending_interval_config_update_event, new_pending_interval_events_execution_event, - new_reconcile_pending_events, + new_advance_epoch_event, new_epoch_transition_start_event, + new_pending_epoch_events_execution_event, new_pending_interval_config_update_event, + new_pending_interval_events_execution_event, new_reconcile_pending_events, }; use mixnet_contract_common::pending_events::PendingIntervalEventKind; -use mixnet_contract_common::{LayerAssignment, MixId}; +use mixnet_contract_common::{EpochState, EpochStatus, LayerAssignment, MixId}; use std::collections::BTreeSet; // those two should be called in separate tx (from advancing epoch), @@ -121,7 +123,8 @@ pub fn try_reconcile_epoch_events( // - somebody sneaks in some extra delegations // - the same person decides to pay the transaction fees and reconcile epoch events themselves // - the validator API distributes the rewards -> this new sneaky delegation is now included in reward calculation! - ensure_is_authorized(info.sender, deps.storage)?; + let mut current_epoch_status = ensure_can_advance_epoch(&info.sender, deps.storage)?; + current_epoch_status.ensure_is_in_event_reconciliation_state()?; let mut response = Response::new().add_event(new_reconcile_pending_events()); @@ -158,6 +161,24 @@ pub fn try_reconcile_epoch_events( .push(new_pending_interval_events_execution_event(executed)); } + // if there are no more events to clear, go into the next state + let pending_events = super::queries::query_number_of_pending_events(deps.as_ref())?; + // we can only progress if there are no epoch events AND if the interval has finished, that there are no interval events + let progress = if pending_events.epoch_events == 0 { + if interval.is_current_interval_over(&env) { + pending_events.interval_events == 0 + } else { + true + } + } else { + false + }; + + if progress { + current_epoch_status.state = EpochState::AdvancingEpoch; + storage::save_current_epoch_status(deps.storage, ¤t_epoch_status)?; + } + Ok(response) } @@ -199,17 +220,66 @@ fn update_rewarded_set( )?) } +pub fn try_begin_epoch_transition( + deps: DepsMut<'_>, + env: Env, + info: MessageInfo, +) -> Result { + // Only the rewarding validator(s) can attempt to advance epoch + ensure_is_authorized(&info.sender, deps.storage)?; + + // can't do pre-mature epoch transition... + let current_interval = storage::current_interval(deps.storage)?; + if !current_interval.is_current_epoch_over(&env) { + return Err(MixnetContractError::EpochInProgress { + current_block_time: env.block.time.seconds(), + epoch_start: current_interval.current_epoch_start_unix_timestamp(), + epoch_end: current_interval.current_epoch_end_unix_timestamp(), + }); + } + + // ensure some other validator (currently not a problem), hasn't already committed to epoch progression + ensure_epoch_in_progress_state(deps.storage)?; + + // Note: if at any point we decide to change our rewarded set to be few thousand nodes + // and the below call fails, we'll have to pass `last_node_in_rewarded_set` as an argument to this function + // and then verify whether the provided value is valid (by using range iterator on `REWARDED_SET` + // and checking if there are any other entries following the provided value) + let rewarded_set = storage::REWARDED_SET + .range(deps.storage, None, None, Order::Ascending) + .map(|kv| kv.map(|kv| kv.0)) + .collect::, _>>()?; + + // if there are no nodes to reward (i.e. empty rewarded set), we go straight into event reconciliation + let new_epoch_state = if let Some(last) = rewarded_set.last() { + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: *last, + } + } else { + EpochState::ReconcilingEvents + }; + + // progress into the first stage of epoch progression + let new_epoch_status = EpochStatus { + being_advanced_by: info.sender, + state: new_epoch_state, + }; + + storage::save_current_epoch_status(deps.storage, &new_epoch_status)?; + Ok(Response::new().add_event(new_epoch_transition_start_event(current_interval))) +} + pub fn try_advance_epoch( - mut deps: DepsMut<'_>, + deps: DepsMut<'_>, env: Env, info: MessageInfo, layer_assignments: Vec, expected_active_set_size: u32, ) -> Result { // Only rewarding validator can attempt to advance epoch - ensure_is_authorized(info.sender, deps.storage)?; - - let mut response = Response::new(); + let mut current_epoch_status = ensure_can_advance_epoch(&info.sender, deps.storage)?; + current_epoch_status.ensure_is_in_advancement_state()?; // we must make sure that we roll into new epoch / interval with up to date state // with no pending actions (like somebody wanting to update their profit margin) @@ -220,29 +290,10 @@ pub fn try_advance_epoch( epoch_start: current_interval.current_epoch_start_unix_timestamp(), epoch_end: current_interval.current_epoch_end_unix_timestamp(), }); - } else { - let (mut sub_response, executed) = - perform_pending_epoch_actions(deps.branch(), &env, None)?; - response.messages.append(&mut sub_response.messages); - response.attributes.append(&mut sub_response.attributes); - response.events.append(&mut sub_response.events); - response - .events - .push(new_pending_epoch_events_execution_event(executed)); } - // first clear epoch events queue and then touch the interval actions + // if the current interval is over, apply reward pool changes if current_interval.is_current_interval_over(&env) { - // the interval has finished -> we can change things such as the profit margin - let (mut sub_response, executed) = - perform_pending_interval_actions(deps.branch(), &env, None)?; - response.messages.append(&mut sub_response.messages); - response.attributes.append(&mut sub_response.attributes); - response.events.append(&mut sub_response.events); - response - .events - .push(new_pending_interval_events_execution_event(executed)); - // this one is a very important one! rewards::helpers::apply_reward_pool_changes(deps.storage)?; } @@ -260,7 +311,10 @@ pub fn try_advance_epoch( update_mixnode_layer(a.mix_id(), a.layer(), deps.storage)?; } - Ok(response.add_event(new_advance_epoch_event(updated_interval, num_nodes as u32))) + current_epoch_status.state = EpochState::InProgress; + storage::save_current_epoch_status(deps.storage, ¤t_epoch_status)?; + + Ok(Response::new().add_event(new_advance_epoch_event(updated_interval, num_nodes as u32))) } pub(crate) fn try_update_interval_config( @@ -273,6 +327,14 @@ pub(crate) fn try_update_interval_config( ) -> Result { ensure_is_owner(info.sender, deps.storage)?; + if epochs_in_interval == 0 { + return Err(MixnetContractError::EpochsInIntervalZero); + } + + if epoch_duration_secs == 0 { + return Err(MixnetContractError::EpochDurationZero); + } + let interval = storage::current_interval(deps.storage)?; if force_immediately || interval.is_current_interval_over(&env) { change_interval_config( @@ -283,6 +345,10 @@ pub(crate) fn try_update_interval_config( epoch_duration_secs, ) } else { + // changing interval config is only allowed if the epoch is currently not in the process of being advanced + // (unless the force flag was used) + ensure_epoch_in_progress_state(deps.storage)?; + // push the interval event let interval_event = PendingIntervalEventKind::UpdateIntervalConfig { epochs_in_interval, @@ -804,6 +870,136 @@ mod tests { } } + #[cfg(test)] + mod beginning_epoch_transition { + use super::*; + use cosmwasm_std::testing::mock_info; + + #[test] + fn returns_error_if_epoch_is_in_progress() { + let mut test = TestSetup::new(); + let env = test.env(); + let rewarding_validator = test.rewarding_validator(); + + let res = try_begin_epoch_transition(test.deps_mut(), env, rewarding_validator); + assert!(matches!( + res, + Err(MixnetContractError::EpochInProgress { .. }) + )) + } + + #[test] + fn can_only_be_performed_if_in_progress_state() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + let res = try_begin_epoch_transition(test.deps_mut(), env, rewarding_validator); + assert_eq!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { + current_state: bad_state + }) + ); + } + } + + #[test] + fn returns_error_if_not_performed_by_the_rewarding_validator() { + let mut test = TestSetup::new(); + let env = test.env(); + + test.skip_to_current_epoch_end(); + + let random = mock_info("alice", &[]); + let owner = test.owner(); + + let res = try_begin_epoch_transition(test.deps_mut(), env.clone(), random); + assert!(matches!(res, Err(MixnetContractError::Unauthorized))); + + let res = try_begin_epoch_transition(test.deps_mut(), env, owner); + assert!(matches!(res, Err(MixnetContractError::Unauthorized))); + } + + #[test] + fn returns_error_if_epoch_is_already_being_advanced() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + try_begin_epoch_transition(test.deps_mut(), env.clone(), rewarding_validator.clone()) + .unwrap(); + + let res = try_begin_epoch_transition(test.deps_mut(), env, rewarding_validator); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + + #[test] + fn epoch_state_is_correctly_updated_for_empty_rewarded_set() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + try_begin_epoch_transition(test.deps_mut(), env, rewarding_validator).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::ReconcilingEvents, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + + #[test] + fn epoch_state_is_correctly_updated_for_nonempty_rewarded_set() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.force_change_rewarded_set(vec![1, 2, 3, 4, 5]); + test.skip_to_current_epoch_end(); + let env = test.env(); + + try_begin_epoch_transition(test.deps_mut(), env, rewarding_validator).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 5, + }, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + } + #[cfg(test)] mod reconciling_epoch_events { use super::*; @@ -816,12 +1012,140 @@ mod tests { use mixnet_contract_common::pending_events::PendingEpochEventKind; use mixnet_contract_common::reward_params::IntervalRewardingParamsUpdate; + #[test] + fn can_only_be_performed_if_in_reconciling_state() { + let bad_states = vec![ + EpochState::InProgress, + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + let res = + try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, None); + assert_eq!( + res, + Err(MixnetContractError::EpochNotInEventReconciliationState { + current_state: bad_state + }) + ); + } + } + + #[test] + fn epoch_state_is_correctly_updated_if_there_are_no_events() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_epoch_end(); + test.set_epoch_reconciliation_state(); + let env = test.env(); + + try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, None).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::AdvancingEpoch, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + + #[test] + fn epoch_state_is_not_updated_if_some_events_are_cleared() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + push_n_dummy_epoch_actions(&mut test, 10); + push_n_dummy_interval_actions(&mut test, 10); + test.set_epoch_reconciliation_state(); + + try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, Some(5)).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::ReconcilingEvents, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + + #[test] + fn epoch_state_is_correctly_updated_if_even_with_leftover_interval_events_if_interval_is_not_over( + ) { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_epoch_end(); + let env = test.env(); + + push_n_dummy_epoch_actions(&mut test, 10); + push_n_dummy_interval_actions(&mut test, 10); + test.set_epoch_reconciliation_state(); + + try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, None).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::AdvancingEpoch, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + + #[test] + fn epoch_state_is_correctly_updated_if_all_events_are_cleared() { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + test.skip_to_current_interval_end(); + let env = test.env(); + + push_n_dummy_epoch_actions(&mut test, 10); + push_n_dummy_interval_actions(&mut test, 10); + test.set_epoch_reconciliation_state(); + + try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, None).unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::AdvancingEpoch, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + #[test] fn returns_error_if_epoch_is_in_progress() { let mut test = TestSetup::new(); let env = test.env(); let rewarding_validator = test.rewarding_validator(); + test.set_epoch_reconciliation_state(); let res = try_reconcile_epoch_events(test.deps_mut(), env, rewarding_validator, None); assert!(matches!( res, @@ -835,6 +1159,7 @@ mod tests { let env = test.env(); test.skip_to_current_epoch_end(); + test.set_epoch_reconciliation_state(); let random = mock_info("alice", &[]); let owner = test.owner(); @@ -853,6 +1178,7 @@ mod tests { push_n_dummy_epoch_actions(&mut test, 10); push_n_dummy_interval_actions(&mut test, 10); test.skip_to_current_epoch_end(); + test.set_epoch_reconciliation_state(); let env = test.env(); let rewarding_validator = test.rewarding_validator(); @@ -872,6 +1198,7 @@ mod tests { push_n_dummy_epoch_actions(&mut test, 10); push_n_dummy_interval_actions(&mut test, 10); test.skip_to_current_interval_end(); + test.set_epoch_reconciliation_state(); let rewarding_validator = test.rewarding_validator(); let env = test.env(); @@ -900,6 +1227,7 @@ mod tests { // all test cases are using the same one let rewarding_validator = test1.rewarding_validator(); + test1.set_epoch_reconciliation_state(); try_reconcile_epoch_events( test1.deps_mut(), env.clone(), @@ -948,6 +1276,7 @@ mod tests { assert!(epoch_events.is_empty()); assert!(interval_events.is_empty()); + test2.set_epoch_reconciliation_state(); try_reconcile_epoch_events( test2.deps_mut(), env.clone(), @@ -972,6 +1301,7 @@ mod tests { assert!(epoch_events.is_empty()); assert!(interval_events.is_empty()); + test3.set_epoch_reconciliation_state(); try_reconcile_epoch_events( test3.deps_mut(), env.clone(), @@ -984,6 +1314,7 @@ mod tests { assert!(epoch_events.is_empty()); assert!(interval_events.is_empty()); + test4.set_epoch_reconciliation_state(); try_reconcile_epoch_events(test4.deps_mut(), env, rewarding_validator, Some(100)) .unwrap(); let epoch_events = test4.pending_epoch_events(); @@ -1040,6 +1371,7 @@ mod tests { expected_events.push(new_pending_interval_events_execution_event(1)); test.skip_to_current_interval_end(); + test.set_epoch_reconciliation_state(); let env = test.env(); let rewarding_validator = test.rewarding_validator(); @@ -1142,15 +1474,97 @@ mod tests { use super::*; use crate::mixnodes::queries::query_mixnode_details; use crate::rewards::models::RewardPoolChange; - use crate::support::tests::fixtures::TEST_COIN_DENOM; use cosmwasm_std::testing::mock_info; - use cosmwasm_std::{coin, coins, BankMsg, Decimal, Empty, SubMsg, Uint128}; - use mixnet_contract_common::events::{ - new_delegation_on_unbonded_node_event, new_rewarding_params_update_event, - }; + use cosmwasm_std::{Decimal, Uint128}; use mixnet_contract_common::reward_params::IntervalRewardingParamsUpdate; use mixnet_contract_common::{Layer, RewardedSetNodeStatus}; + #[test] + fn can_only_be_performed_if_in_advancing_epoch_state() { + let bad_states = vec![ + EpochState::InProgress, + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); + let current_active_set = test.rewarding_params().active_set_size; + + test.skip_to_current_epoch_end(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + let layer_assignments = vec![ + LayerAssignment::new(1, Layer::One), + LayerAssignment::new(2, Layer::Two), + LayerAssignment::new(3, Layer::Three), + ]; + + let env = test.env(); + let sender = test.rewarding_validator(); + let res = try_advance_epoch( + test.deps_mut(), + env, + sender, + layer_assignments, + current_active_set, + ); + assert_eq!( + res, + Err(MixnetContractError::EpochNotInAdvancementState { + current_state: bad_state + }) + ); + } + } + + #[test] + fn epoch_state_is_correctly_updated() { + let mut test = TestSetup::new(); + test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); + let current_active_set = test.rewarding_params().active_set_size; + + test.skip_to_current_epoch_end(); + test.set_epoch_advancement_state(); + + let layer_assignments = vec![ + LayerAssignment::new(1, Layer::One), + LayerAssignment::new(2, Layer::Two), + LayerAssignment::new(3, Layer::Three), + ]; + + let env = test.env(); + let sender = test.rewarding_validator(); + try_advance_epoch( + test.deps_mut(), + env, + sender, + layer_assignments, + current_active_set, + ) + .unwrap(); + + let expected = EpochStatus { + being_advanced_by: test.rewarding_validator().sender, + state: EpochState::InProgress, + }; + assert_eq!( + expected, + storage::current_epoch_status(test.deps().storage).unwrap() + ) + } + #[test] fn can_only_be_performed_by_specified_rewarding_validator() { let mut test = TestSetup::new(); @@ -1161,6 +1575,7 @@ mod tests { let some_sender = mock_info("foomper", &[]); test.skip_to_current_epoch_end(); + test.set_epoch_advancement_state(); let layer_assignments = vec![ LayerAssignment::new(1, Layer::One), @@ -1188,13 +1603,14 @@ mod tests { layer_assignments, current_active_set, ); - println!("{:?}", res); assert!(res.is_ok()) } #[test] fn can_only_be_performed_if_epoch_is_over() { let mut test = TestSetup::new(); + test.set_epoch_advancement_state(); + let current_active_set = test.rewarding_params().active_set_size; test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); @@ -1241,6 +1657,7 @@ mod tests { // sanity check test.skip_to_current_epoch_end(); + let env = test.env(); let res = try_advance_epoch( test.deps_mut(), @@ -1252,171 +1669,11 @@ mod tests { assert!(res.is_ok()) } - #[test] - fn only_clears_epoch_events_if_interval_is_in_progress() { - let mut test = TestSetup::new(); - let current_active_set = test.rewarding_params().active_set_size; - - test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); - - push_n_dummy_epoch_actions(&mut test, 10); - push_n_dummy_interval_actions(&mut test, 10); - test.skip_to_current_epoch_end(); - - let layer_assignments = vec![ - LayerAssignment::new(1, Layer::One), - LayerAssignment::new(2, Layer::Two), - LayerAssignment::new(3, Layer::Three), - ]; - - let env = test.env(); - let sender = test.rewarding_validator(); - try_advance_epoch( - test.deps_mut(), - env, - sender, - layer_assignments, - current_active_set, - ) - .unwrap(); - - let epoch_events = test.pending_epoch_events(); - let interval_events = test.pending_interval_events(); - assert!(epoch_events.is_empty()); - assert_eq!(interval_events.len(), 10); - } - - #[test] - fn clears_both_epoch_and_interval_events_if_interval_has_finished() { - let mut test = TestSetup::new(); - let current_active_set = test.rewarding_params().active_set_size; - - test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); - - push_n_dummy_epoch_actions(&mut test, 10); - push_n_dummy_interval_actions(&mut test, 10); - test.skip_to_current_interval_end(); - - let layer_assignments = vec![ - LayerAssignment::new(1, Layer::One), - LayerAssignment::new(2, Layer::Two), - LayerAssignment::new(3, Layer::Three), - ]; - - let env = test.env(); - let sender = test.rewarding_validator(); - try_advance_epoch( - test.deps_mut(), - env, - sender, - layer_assignments, - current_active_set, - ) - .unwrap(); - - let epoch_events = test.pending_epoch_events(); - let interval_events = test.pending_interval_events(); - assert!(epoch_events.is_empty()); - assert!(interval_events.is_empty()); - } - - #[test] - fn if_executes_any_events_it_propagates_responses() { - let mut test = TestSetup::new(); - let env = test.env(); - let current_active_set = test.rewarding_params().active_set_size; - - test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); - test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); - - let mut expected_events = Vec::new(); - let mut expected_messages: Vec> = Vec::new(); - - let non_existent_delegation = PendingEpochEventKind::Delegate { - owner: Addr::unchecked("foomp"), - mix_id: 123, - amount: coin(123, TEST_COIN_DENOM), - proxy: None, - }; - storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation) - .unwrap(); - expected_events.push(new_delegation_on_unbonded_node_event( - &Addr::unchecked("foomp"), - &None, - 123, - )); - expected_messages.push(SubMsg::new(BankMsg::Send { - to_address: "foomp".to_string(), - amount: coins(123, TEST_COIN_DENOM), - })); - expected_events.push(new_pending_epoch_events_execution_event(1)); - - // interval event - let update = IntervalRewardingParamsUpdate { - rewarded_set_size: Some(500), - ..Default::default() - }; - let change_params = PendingIntervalEventKind::UpdateRewardingParams { update }; - storage::push_new_interval_event(test.deps_mut().storage, &env, change_params).unwrap(); - let interval = test.current_interval(); - let mut expected_updated = test.rewarding_params(); - expected_updated - .try_apply_updates(update, interval.epochs_in_interval()) - .unwrap(); - expected_events.push(new_rewarding_params_update_event( - env.block.height, - update, - expected_updated.interval, - )); - expected_events.push(new_pending_interval_events_execution_event(1)); - let current_interval = test.current_interval(); - let expected = current_interval.advance_epoch(); - expected_events.push(new_advance_epoch_event(expected, 3)); - - test.skip_to_current_interval_end(); - - let layer_assignments = vec![ - LayerAssignment::new(1, Layer::One), - LayerAssignment::new(2, Layer::Two), - LayerAssignment::new(3, Layer::Three), - ]; - - let env = test.env(); - let sender = test.rewarding_validator(); - let res = try_advance_epoch( - test.deps_mut(), - env, - sender, - layer_assignments, - current_active_set, - ) - .unwrap(); - - let mut expected = Response::new().add_events(expected_events); - expected.messages = expected_messages; - assert_eq!(res, expected); - assert_eq!( - 1, - storage::LAST_PROCESSED_EPOCH_EVENT - .load(test.deps().storage) - .unwrap() - ); - assert_eq!( - 1, - storage::LAST_PROCESSED_INTERVAL_EVENT - .load(test.deps().storage) - .unwrap() - ); - } - #[test] fn if_interval_is_over_applies_reward_pool_changes() { let mut test = TestSetup::new(); + test.set_epoch_advancement_state(); + let current_active_set = test.rewarding_params().active_set_size; test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); @@ -1446,6 +1703,7 @@ mod tests { // end of epoch - nothing has happened let sender = test.rewarding_validator(); test.skip_to_current_epoch_end(); + let env = test.env(); try_advance_epoch( test.deps_mut(), @@ -1465,6 +1723,8 @@ mod tests { let sender = test.rewarding_validator(); test.skip_to_current_interval_end(); + test.set_epoch_advancement_state(); + let env = test.env(); try_advance_epoch( test.deps_mut(), @@ -1497,6 +1757,8 @@ mod tests { #[test] fn updates_rewarded_set_and_interval_data() { let mut test = TestSetup::new(); + test.set_epoch_advancement_state(); + let current_active_set = test.rewarding_params().active_set_size; test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); @@ -1548,6 +1810,52 @@ mod tests { use cosmwasm_std::Decimal; use std::time::Duration; + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let owner = test.owner(); + let env = test.env(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + + storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + let res = try_update_interval_config( + test.deps_mut(), + env.clone(), + owner.clone(), + 100, + 1000, + false, + ); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + + let res_forced = try_update_interval_config( + test.deps_mut(), + env.clone(), + owner, + 100, + 1000, + true, + ); + assert!(res_forced.is_ok()) + } + } + #[test] fn can_only_be_done_by_contract_owner() { let mut test = TestSetup::new(); diff --git a/contracts/mixnet/src/lib.rs b/contracts/mixnet/src/lib.rs index 6f420d3541..fb98e68fe0 100644 --- a/contracts/mixnet/src/lib.rs +++ b/contracts/mixnet/src/lib.rs @@ -12,6 +12,7 @@ mod gateways; mod interval; mod mixnet_contract_settings; mod mixnodes; +mod queued_migrations; mod rewards; mod support; diff --git a/contracts/mixnet/src/mixnet_contract_settings/queries.rs b/contracts/mixnet/src/mixnet_contract_settings/queries.rs index 1181c87d2f..569f266bd8 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/queries.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/queries.rs @@ -28,9 +28,13 @@ pub(crate) fn query_contract_version() -> ContractBuildInformation { ContractBuildInformation { build_timestamp: env!("VERGEN_BUILD_TIMESTAMP").to_string(), build_version: env!("VERGEN_BUILD_SEMVER").to_string(), - commit_sha: env!("VERGEN_GIT_SHA").to_string(), - commit_timestamp: env!("VERGEN_GIT_COMMIT_TIMESTAMP").to_string(), - commit_branch: env!("VERGEN_GIT_BRANCH").to_string(), + commit_sha: option_env!("VERGEN_GIT_SHA").unwrap_or("NONE").to_string(), + commit_timestamp: option_env!("VERGEN_GIT_COMMIT_TIMESTAMP") + .unwrap_or("NONE") + .to_string(), + commit_branch: option_env!("VERGEN_GIT_BRANCH") + .unwrap_or("NONE") + .to_string(), rustc_version: env!("VERGEN_RUSTC_SEMVER").to_string(), } } diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index 1d0da0449f..d31dbbf13e 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -10,8 +10,9 @@ use crate::mixnodes::helpers::{ get_mixnode_details_by_owner, must_get_mixnode_bond_by_owner, save_new_mixnode, }; use crate::support::helpers::{ - ensure_bonded, ensure_is_authorized, ensure_no_existing_bond, ensure_proxy_match, - validate_node_identity_signature, validate_pledge, + ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond, + ensure_proxy_match, ensure_sent_by_vesting_contract, validate_node_identity_signature, + validate_pledge, }; use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage}; use mixnet_contract_common::error::MixnetContractError; @@ -47,7 +48,7 @@ pub fn assign_mixnode_layer( mix_id: MixId, layer: Layer, ) -> Result { - ensure_is_authorized(info.sender, deps.storage)?; + ensure_is_authorized(&info.sender, deps.storage)?; update_mixnode_layer(mix_id, layer, deps.storage)?; @@ -83,6 +84,8 @@ pub fn try_add_mixnode_on_behalf( owner: String, owner_signature: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let proxy = info.sender; let owner = deps.api.addr_validate(&owner)?; _try_add_mixnode( @@ -116,7 +119,7 @@ fn _try_add_mixnode( // if the client has an active bonded mixnode or gateway, don't allow bonding // note that this has to be done explicitly as `UniqueIndex` constraint would not protect us // against attempting to use different node types (i.e. gateways and mixnodes) - ensure_no_existing_bond(deps.storage, &owner)?; + ensure_no_existing_bond(&owner, deps.storage)?; // there's no need to explicitly check whether there already exists mixnode with the same // identity or sphinx keys as this is going to be done implicitly when attempting to save @@ -165,6 +168,8 @@ pub fn try_increase_pledge_on_behalf( info: MessageInfo, owner: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let proxy = info.sender; let owner = deps.api.addr_validate(&owner)?; _try_increase_pledge(deps, env, info.funds, owner, Some(proxy)) @@ -181,6 +186,9 @@ pub fn _try_increase_pledge( .ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?; let mix_id = mix_details.mix_id(); + // increasing pledge is only allowed if the epoch is currently not in the process of being advanced + ensure_epoch_in_progress_state(deps.storage)?; + ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?; ensure_bonded(&mix_details.bond_information)?; @@ -205,6 +213,8 @@ pub fn try_remove_mixnode_on_behalf( info: MessageInfo, owner: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let proxy = info.sender; let owner = deps.api.addr_validate(&owner)?; _try_remove_mixnode(deps, env, owner, Some(proxy)) @@ -231,6 +241,9 @@ pub(crate) fn _try_remove_mixnode( .ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })? .1; + // unbonding is only allowed if the epoch is currently not in the process of being advanced + ensure_epoch_in_progress_state(deps.storage)?; + // see if the proxy matches ensure_proxy_match(&proxy, &existing_bond.proxy)?; ensure_bonded(&existing_bond)?; @@ -276,6 +289,8 @@ pub(crate) fn try_update_mixnode_config_on_behalf( new_config: MixNodeConfigUpdate, owner: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let owner = deps.api.addr_validate(&owner)?; let proxy = info.sender; _try_update_mixnode_config(deps, new_config, owner, Some(proxy)) @@ -329,6 +344,8 @@ pub(crate) fn try_update_mixnode_cost_params_on_behalf( new_costs: MixNodeCostParams, owner: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let owner = deps.api.addr_validate(&owner)?; let proxy = info.sender; _try_update_mixnode_cost_params(deps, env, new_costs, owner, Some(proxy)) @@ -344,6 +361,9 @@ pub(crate) fn _try_update_mixnode_cost_params( // see if the node still exists let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?; + // changing cost params is only allowed if the epoch is currently not in the process of being advanced + ensure_epoch_in_progress_state(deps.storage)?; + ensure_proxy_match(&proxy, &existing_bond.proxy)?; ensure_bonded(&existing_bond)?; @@ -371,10 +391,13 @@ pub mod tests { use crate::mixnet_contract_settings::storage::minimum_mixnode_pledge; use crate::mixnodes::helpers::get_mixnode_details_by_id; use crate::support::tests::fixtures::{good_mixnode_pledge, TEST_COIN_DENOM}; + use crate::support::tests::test_helpers::TestSetup; use crate::support::tests::{fixtures, test_helpers}; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{Order, StdResult, Uint128}; - use mixnet_contract_common::{ExecuteMsg, Layer, LayerDistribution, Percent}; + use mixnet_contract_common::{ + EpochState, EpochStatus, ExecuteMsg, Layer, LayerDistribution, Percent, + }; #[test] fn mixnode_add() { @@ -497,51 +520,110 @@ pub mod tests { } #[test] - fn mixnode_remove() { - let mut deps = test_helpers::init_contract(); - let env = mock_env(); - let mut rng = test_helpers::test_rng(); + fn mixnode_add_with_illegal_proxy() { + let mut test = TestSetup::new(); + let env = test.env(); - let sender = "alice"; - let info = mock_info(sender, &[]); + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let owner = "alice"; + let (mixnode, sig, _) = test_helpers::mixnode_with_signature(&mut test.rng, owner); + let cost_params = fixtures::mix_node_cost_params_fixture(); + + // we are informed that we didn't send enough funds + let res = try_add_mixnode_on_behalf( + test.deps_mut(), + env, + mock_info(illegal_proxy.as_ref(), &good_mixnode_pledge()), + mixnode, + cost_params, + owner.to_string(), + sig, + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + + #[test] + fn removing_mixnode_cant_be_performed_if_epoch_transition_is_in_progress() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let env = test.env(); + let owner = "alice"; + let info = mock_info(owner, &[]); + + test.add_dummy_mixnode(owner, None); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + let res = try_remove_mixnode(test.deps_mut(), env.clone(), info); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + } + + #[test] + fn mixnode_remove() { + let mut test = TestSetup::new(); + let env = test.env(); + + let owner = "alice"; + let info = mock_info(owner, &[]); // trying to remove your mixnode fails if you never had one in the first place - let res = try_remove_mixnode(deps.as_mut(), env.clone(), info.clone()); + let res = try_remove_mixnode(test.deps_mut(), env.clone(), info.clone()); assert_eq!( res, Err(MixnetContractError::NoAssociatedMixNodeBond { - owner: Addr::unchecked(sender) + owner: Addr::unchecked(owner) }) ); - let mix_id = test_helpers::add_mixnode( - &mut rng, - deps.as_mut(), - env.clone(), - sender, - good_mixnode_pledge(), - ); + let mix_id = test.add_dummy_mixnode(owner, None); + let vesting_contract = test.vesting_contract(); // attempted to remove on behalf with invalid proxy (current is `None`) let res = try_remove_mixnode_on_behalf( - deps.as_mut(), + test.deps_mut(), env.clone(), - mock_info("proxy", &[]), - sender.to_string(), + mock_info(vesting_contract.as_ref(), &[]), + owner.to_string(), ); + assert_eq!( res, Err(MixnetContractError::ProxyMismatch { existing: "None".to_string(), - incoming: "proxy".to_string() + incoming: vesting_contract.into_string() }) ); // "normal" unbonding succeeds and unbonding event is pushed to the pending epoch events - let res = try_remove_mixnode(deps.as_mut(), env.clone(), info.clone()); + let res = try_remove_mixnode(test.deps_mut(), env.clone(), info.clone()); assert!(res.is_ok()); let mut pending_events = interval_storage::PENDING_EPOCH_EVENTS - .range(deps.as_ref().storage, None, None, Order::Ascending) + .range(test.deps().storage, None, None, Order::Ascending) .collect::>>() .unwrap(); assert_eq!(pending_events.len(), 1); @@ -553,18 +635,46 @@ pub mod tests { ); // but fails if repeated (since the node is already in the "unbonding" state)( - let res = try_remove_mixnode(deps.as_mut(), env, info); + let res = try_remove_mixnode(test.deps_mut(), env, info); assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id })) } #[test] - fn updating_mixnode_config() { - let mut deps = test_helpers::init_contract(); - let env = mock_env(); - let mut rng = test_helpers::test_rng(); + fn mixnode_remove_with_illegal_proxy() { + let mut test = TestSetup::new(); + let env = test.env(); - let sender = "alice"; - let info = mock_info(sender, &[]); + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let owner = "alice"; + + test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone()); + + let res = try_remove_mixnode_on_behalf( + test.deps_mut(), + env, + mock_info(illegal_proxy.as_ref(), &[]), + owner.to_string(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + + #[test] + fn updating_mixnode_config() { + let mut test = TestSetup::new(); + let env = test.env(); + + let owner = "alice"; + let info = mock_info(owner, &[]); let update = MixNodeConfigUpdate { host: "1.1.1.1:1234".to_string(), mix_port: 1234, @@ -574,43 +684,38 @@ pub mod tests { }; // try updating a non existing mixnode bond - let res = try_update_mixnode_config(deps.as_mut(), info.clone(), update.clone()); + let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone()); assert_eq!( res, Err(MixnetContractError::NoAssociatedMixNodeBond { - owner: Addr::unchecked(sender) + owner: Addr::unchecked(owner) }) ); - let mix_id = test_helpers::add_mixnode( - &mut rng, - deps.as_mut(), - env.clone(), - sender, - tests::fixtures::good_mixnode_pledge(), - ); + let mix_id = test.add_dummy_mixnode(owner, None); + let vesting_contract = test.vesting_contract(); // attempted to remove on behalf with invalid proxy (current is `None`) let res = try_update_mixnode_config_on_behalf( - deps.as_mut(), - mock_info("proxy", &[]), + test.deps_mut(), + mock_info(vesting_contract.as_ref(), &[]), update.clone(), - sender.to_string(), + owner.to_string(), ); assert_eq!( res, Err(MixnetContractError::ProxyMismatch { existing: "None".to_string(), - incoming: "proxy".to_string() + incoming: vesting_contract.into_string() }) ); // "normal" update succeeds - let res = try_update_mixnode_config(deps.as_mut(), info.clone(), update.clone()); + let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone()); assert!(res.is_ok()); // and the config has actually been updated - let mix = must_get_mixnode_bond_by_owner(deps.as_ref().storage, &Addr::unchecked(sender)) - .unwrap(); + let mix = + must_get_mixnode_bond_by_owner(test.deps().storage, &Addr::unchecked(owner)).unwrap(); assert_eq!(mix.mix_node.host, update.host); assert_eq!(mix.mix_node.mix_port, update.mix_port); assert_eq!(mix.mix_node.verloc_port, update.verloc_port); @@ -618,19 +723,90 @@ pub mod tests { assert_eq!(mix.mix_node.version, update.version); // but we cannot perform any updates whilst the mixnode is already unbonding - try_remove_mixnode(deps.as_mut(), env, info.clone()).unwrap(); - let res = try_update_mixnode_config(deps.as_mut(), info, update); + try_remove_mixnode(test.deps_mut(), env, info.clone()).unwrap(); + let res = try_update_mixnode_config(test.deps_mut(), info, update); assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id })) } #[test] - fn updating_mixnode_cost_params() { - let mut deps = test_helpers::init_contract(); - let env = mock_env(); - let mut rng = test_helpers::test_rng(); + fn updating_mixnode_config_with_illegal_proxy() { + let mut test = TestSetup::new(); - let sender = "alice"; - let info = mock_info(sender, &[]); + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let owner = "alice"; + + test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone()); + let update = MixNodeConfigUpdate { + host: "1.1.1.1:1234".to_string(), + mix_port: 1234, + verloc_port: 1235, + http_api_port: 1236, + version: "v1.2.3".to_string(), + }; + + let res = try_update_mixnode_config_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &[]), + update, + owner.to_string(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + + #[test] + fn mixnode_cost_params_cant_be_updated_when_epoch_transition_is_in_progress() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + let update = MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(42).unwrap(), + interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM), + }; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let env = test.env(); + let owner = "alice"; + let info = mock_info(owner, &[]); + + test.add_dummy_mixnode(owner, None); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status).unwrap(); + + let res = + try_update_mixnode_cost_params(test.deps_mut(), env.clone(), info, update.clone()); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + } + + #[test] + fn updating_mixnode_cost_params() { + let mut test = TestSetup::new(); + let env = test.env(); + + let owner = "alice"; + let info = mock_info(owner, &[]); let update = MixNodeCostParams { profit_margin_percent: Percent::from_percentage_value(42).unwrap(), interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM), @@ -638,7 +814,7 @@ pub mod tests { // try updating a non existing mixnode bond let res = try_update_mixnode_cost_params( - deps.as_mut(), + test.deps_mut(), env.clone(), info.clone(), update.clone(), @@ -646,36 +822,31 @@ pub mod tests { assert_eq!( res, Err(MixnetContractError::NoAssociatedMixNodeBond { - owner: Addr::unchecked(sender) + owner: Addr::unchecked(owner) }) ); - let mix_id = test_helpers::add_mixnode( - &mut rng, - deps.as_mut(), - env.clone(), - sender, - tests::fixtures::good_mixnode_pledge(), - ); + let mix_id = test.add_dummy_mixnode(owner, None); + let vesting_contract = test.vesting_contract(); // attempted to remove on behalf with invalid proxy (current is `None`) let res = try_update_mixnode_cost_params_on_behalf( - deps.as_mut(), + test.deps_mut(), env.clone(), - mock_info("proxy", &[]), + mock_info(vesting_contract.as_ref(), &[]), update.clone(), - sender.to_string(), + owner.to_string(), ); assert_eq!( res, Err(MixnetContractError::ProxyMismatch { existing: "None".to_string(), - incoming: "proxy".to_string() + incoming: vesting_contract.into_string() }) ); // "normal" update succeeds let res = try_update_mixnode_cost_params( - deps.as_mut(), + test.deps_mut(), env.clone(), info.clone(), update.clone(), @@ -684,7 +855,7 @@ pub mod tests { // see if the event has been pushed onto the queue let mut pending_events = interval_storage::PENDING_INTERVAL_EVENTS - .range(deps.as_ref().storage, None, None, Order::Ascending) + .range(test.deps().storage, None, None, Order::Ascending) .collect::>>() .unwrap(); assert_eq!(pending_events.len(), 1); @@ -699,27 +870,61 @@ pub mod tests { ); // execute the event - test_helpers::execute_all_pending_events(deps.as_mut(), env.clone()); + test_helpers::execute_all_pending_events(test.deps_mut(), env.clone()); // and see if the config has actually been updated - let mix = get_mixnode_details_by_id(deps.as_ref().storage, mix_id) + let mix = get_mixnode_details_by_id(test.deps().storage, mix_id) .unwrap() .unwrap(); assert_eq!(mix.rewarding_details.cost_params, update); // but we cannot perform any updates whilst the mixnode is already unbonding - try_remove_mixnode(deps.as_mut(), env.clone(), info.clone()).unwrap(); - let res = try_update_mixnode_cost_params(deps.as_mut(), env, info, update); + try_remove_mixnode(test.deps_mut(), env.clone(), info.clone()).unwrap(); + let res = try_update_mixnode_cost_params(test.deps_mut(), env, info, update); assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id })) } + #[test] + fn updating_mixnode_cost_params_with_illegal_proxy() { + let mut test = TestSetup::new(); + let env = test.env(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let owner = "alice"; + + test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone()); + let update = MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value(42).unwrap(), + interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM), + }; + + let res = try_update_mixnode_cost_params_on_behalf( + test.deps_mut(), + env, + mock_info(illegal_proxy.as_ref(), &[]), + update, + owner.to_string(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } + #[test] fn adding_mixnode_with_duplicate_sphinx_key_errors_out() { let mut deps = test_helpers::init_contract(); let mut rng = test_helpers::test_rng(); - let keypair1 = crypto::asymmetric::identity::KeyPair::new(&mut rng); - let keypair2 = crypto::asymmetric::identity::KeyPair::new(&mut rng); + let keypair1 = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng); + let keypair2 = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng); let sig1 = keypair1.private_key().sign_text("alice"); let sig2 = keypair1.private_key().sign_text("bob"); @@ -731,7 +936,7 @@ pub mod tests { mix_port: 1234, verloc_port: 1234, http_api_port: 1234, - sphinx_key: crypto::asymmetric::encryption::KeyPair::new(&mut rng) + sphinx_key: nym_crypto::asymmetric::encryption::KeyPair::new(&mut rng) .public_key() .to_base58_string(), identity_key: keypair1.public_key().to_base58_string(), @@ -770,6 +975,40 @@ pub mod tests { setup_mix_combinations, OWNER_UNBONDED, OWNER_UNBONDED_LEFTOVER, OWNER_UNBONDING, }; use crate::support::tests::test_helpers::TestSetup; + use mixnet_contract_common::{EpochState, EpochStatus}; + + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let env = test.env(); + let owner = "mix-owner"; + + test.add_dummy_mixnode(owner, None); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + let sender = mock_info(owner, &[test.coin(1000)]); + let res = try_increase_pledge(test.deps_mut(), env, sender); + + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + } + } #[test] fn is_not_allowed_if_account_doesnt_own_mixnode() { @@ -797,7 +1036,11 @@ pub mod tests { let wrong_proxy = Addr::unchecked("unrelated-proxy"); test.add_dummy_mixnode(owner_without_proxy.as_str(), None); - test.add_dummy_mixnode_with_proxy(owner_with_proxy.as_str(), None, proxy.clone()); + test.add_dummy_mixnode_with_illegal_proxy( + owner_with_proxy.as_str(), + None, + proxy.clone(), + ); let res = _try_increase_pledge( test.deps_mut(), @@ -945,4 +1188,33 @@ pub mod tests { ); } } + + #[test] + fn fails_for_illegal_proxy() { + let mut test = TestSetup::new(); + let env = test.env(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let owner = "alice"; + + test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone()); + + let res = try_increase_pledge_on_behalf( + test.deps_mut(), + env, + mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), + owner.to_string(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } } diff --git a/contracts/mixnet/src/queued_migrations.rs b/contracts/mixnet/src/queued_migrations.rs index 87d5c392c8..596179f6e6 100644 --- a/contracts/mixnet/src/queued_migrations.rs +++ b/contracts/mixnet/src/queued_migrations.rs @@ -1,2 +1,18 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 + +use crate::interval::storage as interval_storage; +use cosmwasm_std::Storage; +use mixnet_contract_common::error::MixnetContractError; +use mixnet_contract_common::EpochStatus; + +pub(crate) fn create_epoch_status(storage: &mut dyn Storage) -> Result<(), MixnetContractError> { + let current_rewarding_validator = + crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)?; + interval_storage::save_current_epoch_status( + storage, + &EpochStatus::new(current_rewarding_validator), + )?; + + Ok(()) +} diff --git a/contracts/mixnet/src/rewards/helpers.rs b/contracts/mixnet/src/rewards/helpers.rs index 91b8fbdeb0..df2b315ef1 100644 --- a/contracts/mixnet/src/rewards/helpers.rs +++ b/contracts/mixnet/src/rewards/helpers.rs @@ -1,4 +1,4 @@ -// Copyright 2021-2022 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use super::storage; @@ -8,7 +8,20 @@ use cosmwasm_std::{Coin, Storage}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::helpers::IntoBaseDecimal; use mixnet_contract_common::mixnode::{MixNodeDetails, MixNodeRewarding}; -use mixnet_contract_common::Delegation; +use mixnet_contract_common::{Delegation, EpochState, EpochStatus, MixId}; + +pub(crate) fn update_and_save_last_rewarded( + storage: &mut dyn Storage, + mut current_epoch_status: EpochStatus, + new_last_rewarded: MixId, +) -> Result<(), MixnetContractError> { + let is_done = current_epoch_status.update_last_rewarded(new_last_rewarded)?; + if is_done { + current_epoch_status.state = EpochState::ReconcilingEvents + } + interval_storage::save_current_epoch_status(storage, ¤t_epoch_status)?; + Ok(()) +} /// Recomputes rewarding parameters (such as staking supply, saturation point, etc) based on /// pending changes currently stored in `PENDING_REWARD_POOL_CHANGE`. @@ -215,14 +228,14 @@ mod tests { assert_eq!(res.amount, Uint128::zero()); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - let dist1 = test.reward_with_distribution(mix_id, performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + let dist1 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); test.skip_to_next_epoch_end(); - let dist3 = test.reward_with_distribution(mix_id, performance(100.0)); + let dist3 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); let mix_details = get_mixnode_details_by_id(test.deps().storage, mix_id) .unwrap() @@ -254,14 +267,14 @@ mod tests { assert_eq!(res.amount, Uint128::zero()); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - let dist1 = test.reward_with_distribution(mix_id, performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + let dist1 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); test.skip_to_next_epoch_end(); - let dist2 = test.reward_with_distribution(mix_id, performance(100.0)); + let dist2 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); test.skip_to_next_epoch_end(); - let dist3 = test.reward_with_distribution(mix_id, performance(100.0)); + let dist3 = test.reward_with_distribution_with_state_bypass(mix_id, performance(100.0)); let delegation_pre = test.delegation(mix_id, delegator, &None); let mix_rewarding = test.mix_rewarding(mix_id); diff --git a/contracts/mixnet/src/rewards/queries.rs b/contracts/mixnet/src/rewards/queries.rs index d49b1165c6..3804f83da7 100644 --- a/contracts/mixnet/src/rewards/queries.rs +++ b/contracts/mixnet/src/rewards/queries.rs @@ -297,10 +297,13 @@ mod tests { let mix_id = test.add_dummy_mixnode(owner, Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.operator; let res = query_pending_operator_reward(test.deps(), owner.into()).unwrap(); @@ -317,7 +320,10 @@ mod tests { // reward it few more times for good measure for _ in 0..10 { test.skip_to_next_epoch_end(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.operator; let res = query_pending_operator_reward(test.deps(), owner.into()).unwrap(); @@ -341,10 +347,13 @@ mod tests { let mix_id = test.add_dummy_mixnode(owner, Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.operator; let sender = mock_info(owner, &[]); @@ -370,9 +379,12 @@ mod tests { let mix_id = test.add_dummy_mixnode(owner, Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let sender = mock_info(owner, &[]); let env = test.env(); @@ -447,10 +459,13 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.delegates; let res = @@ -466,7 +481,10 @@ mod tests { // reward it few more times for good measure for _ in 0..10 { test.skip_to_next_epoch_end(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.delegates; let res = query_pending_delegator_reward(test.deps(), owner.into(), mix_id, None) @@ -491,10 +509,13 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.delegates; let sender = mock_info("mix-owner", &[]); @@ -521,10 +542,13 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mut total_earned = Decimal::zero(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); total_earned += dist.delegates; let sender = mock_info("mix-owner", &[]); @@ -557,34 +581,58 @@ mod tests { test.add_immediate_delegation(del2, 150_000_000u32, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.add_immediate_delegation(del3, 500_000_000u32, mix_id); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(85.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(85.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(5.0)); + test.reward_with_distribution_with_state_bypass(mix_id, test_helpers::performance(5.0)); test.add_immediate_delegation(del4, 5_000_000u32, mix_id); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.add_immediate_delegation(del2, 250_000_000u32, mix_id); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(98.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(98.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); test.remove_immediate_delegation(del3, mix_id); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(98.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(98.0), + ); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let pending1 = query_pending_delegator_reward(test.deps(), del1.into(), mix_id, None).unwrap(); @@ -660,8 +708,11 @@ mod tests { let mix_id = test.add_dummy_mixnode(owner, Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let sender = mock_info(owner, &[]); let env = test.env(); @@ -686,8 +737,11 @@ mod tests { let mix_id = test.add_dummy_mixnode(owner, Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let sender = mock_info(owner, &[]); let env = test.env(); @@ -710,9 +764,12 @@ mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); - test.update_rewarded_set(vec![]); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); + test.force_change_rewarded_set(vec![]); let res = query_estimated_current_epoch_operator_reward( test.deps(), @@ -732,8 +789,11 @@ mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let res = query_estimated_current_epoch_operator_reward( test.deps(), @@ -753,8 +813,11 @@ mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", Some(initial_stake)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let mix_rewarding = test.mix_rewarding(mix_id); let res = query_estimated_current_epoch_operator_reward( @@ -765,7 +828,10 @@ mod tests { .unwrap(); test.skip_to_next_epoch_end(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(95.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(95.0), + ); let expected = EstimatedCurrentEpochRewardResponse { original_stake: Some(coin(initial_stake.u128(), TEST_COIN_DENOM)), @@ -816,8 +882,11 @@ mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let res = query_estimated_current_epoch_delegator_reward( test.deps(), @@ -841,8 +910,11 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let sender = mock_info("mix-owner", &[]); let env = test.env(); @@ -871,8 +943,11 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let sender = mock_info("mix-owner", &[]); let env = test.env(); @@ -902,9 +977,12 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); - test.update_rewarded_set(vec![]); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); + test.force_change_rewarded_set(vec![]); let res = query_estimated_current_epoch_delegator_reward( test.deps(), @@ -929,8 +1007,11 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(100.0), + ); let res = query_estimated_current_epoch_delegator_reward( test.deps(), @@ -955,7 +1036,7 @@ mod tests { test.add_immediate_delegation(owner, initial_stake, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let mix_rewarding = test.mix_rewarding(mix_id); let res = query_estimated_current_epoch_delegator_reward( @@ -968,7 +1049,10 @@ mod tests { .unwrap(); test.skip_to_next_epoch_end(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(95.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(95.0), + ); let expected = EstimatedCurrentEpochRewardResponse { original_stake: Some(coin(initial_stake.u128(), TEST_COIN_DENOM)), @@ -1003,12 +1087,18 @@ mod tests { test.add_immediate_delegation(del2, initial_stake2, mix_id); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); - test.reward_with_distribution(mix_id, test_helpers::performance(95.0)); + test.force_change_rewarded_set(vec![mix_id]); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(95.0), + ); test.add_immediate_delegation(del3, initial_stake3, mix_id); test.skip_to_next_epoch_end(); - test.reward_with_distribution(mix_id, test_helpers::performance(85.0)); + test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(85.0), + ); let mix_rewarding = test.mix_rewarding(mix_id); @@ -1045,7 +1135,10 @@ mod tests { let cur3 = initial_stake3_dec + est3; test.skip_to_next_epoch_end(); - let dist = test.reward_with_distribution(mix_id, test_helpers::performance(95.0)); + let dist = test.reward_with_distribution_with_state_bypass( + mix_id, + test_helpers::performance(95.0), + ); let share1 = cur1 / mix_rewarding.delegates * dist.delegates; let share2 = cur2 / mix_rewarding.delegates * dist.delegates; diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index e25e4c1bd8..a9ef3b6f5a 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -1,4 +1,4 @@ -// Copyright 2021-2022 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use super::storage; @@ -9,9 +9,10 @@ use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::mixnodes::helpers::get_mixnode_details_by_owner; use crate::mixnodes::storage as mixnodes_storage; use crate::rewards::helpers; +use crate::rewards::helpers::update_and_save_last_rewarded; use crate::support::helpers::{ - ensure_bonded, ensure_is_authorized, ensure_is_owner, ensure_proxy_match, - send_to_proxy_or_owner, + ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner, + ensure_proxy_match, ensure_sent_by_vesting_contract, send_to_proxy_or_owner, }; use cosmwasm_std::{wasm_execute, Addr, DepsMut, Env, MessageInfo, Response}; use mixnet_contract_common::error::MixnetContractError; @@ -26,7 +27,7 @@ use mixnet_contract_common::pending_events::{PendingEpochEventKind, PendingInter use mixnet_contract_common::reward_params::{ IntervalRewardingParamsUpdate, NodeRewardParams, Performance, }; -use mixnet_contract_common::{Delegation, MixId}; +use mixnet_contract_common::{Delegation, EpochState, MixId}; use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; pub(crate) fn try_reward_mixnode( @@ -36,7 +37,9 @@ pub(crate) fn try_reward_mixnode( mix_id: MixId, node_performance: Performance, ) -> Result { - ensure_is_authorized(info.sender, deps.storage)?; + // check whether this `info.sender` is the same one as set in `epoch_status.being_advanced_by` + // if so, return `epoch_status` so we could avoid having to perform extra read from the storage + let current_epoch_status = ensure_can_advance_epoch(&info.sender, deps.storage)?; // see if the epoch has finished let interval = interval_storage::current_interval(deps.storage)?; @@ -47,6 +50,19 @@ pub(crate) fn try_reward_mixnode( epoch_end: interval.current_epoch_end_unix_timestamp(), }); } + let absolute_epoch_id = interval.current_epoch_absolute_id(); + + if matches!(current_epoch_status.state, EpochState::Rewarding {last_rewarded, ..} if last_rewarded == mix_id) + { + return Err(MixnetContractError::MixnodeAlreadyRewarded { + mix_id, + absolute_epoch_id, + }); + } + + // update the epoch state with this node as being rewarded most recently + // (if the transaction fails down the line, it will be reverted) + update_and_save_last_rewarded(deps.storage, current_epoch_status, mix_id)?; // there's a chance of this failing to load the details if the mixnode unbonded before rewards // were distributed and all of its delegators are also gone @@ -64,8 +80,7 @@ pub(crate) fn try_reward_mixnode( // check if this node has already been rewarded for the current epoch. // unlike the previous check, this one should be a hard error since this cannot be - // influenced by users actions - let absolute_epoch_id = interval.current_epoch_absolute_id(); + // influenced by users actions (note that previous epoch state checks should actually already guard us against it) if absolute_epoch_id == mix_rewarding.last_rewarded_epoch { return Err(MixnetContractError::MixnodeAlreadyRewarded { mix_id, @@ -130,6 +145,8 @@ pub(crate) fn try_withdraw_operator_reward_on_behalf( info: MessageInfo, owner: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let proxy = info.sender; let owner = deps.api.addr_validate(&owner)?; _try_withdraw_operator_reward(deps, owner, Some(proxy)) @@ -195,6 +212,8 @@ pub(crate) fn try_withdraw_delegator_reward_on_behalf( mix_id: MixId, owner: String, ) -> Result { + ensure_sent_by_vesting_contract(&info, deps.storage)?; + let proxy = info.sender; let owner = deps.api.addr_validate(&owner)?; _try_withdraw_delegator_reward(deps, mix_id, owner, Some(proxy)) @@ -293,6 +312,10 @@ pub(crate) fn try_update_active_set_size( active_set_size, ))) } else { + // updating active sety size is only allowed if the epoch is currently not in the process of being advanced + // (unless the force flag was used) + ensure_epoch_in_progress_state(deps.storage)?; + // push the epoch event let epoch_event = PendingEpochEventKind::UpdateActiveSetSize { new_size: active_set_size, @@ -332,6 +355,10 @@ pub(crate) fn try_update_rewarding_params( rewarding_params.interval, ))) } else { + // changing rewarding parameters is only allowed if the epoch is currently not in the process of being advanced + // (unless the force flag was used) + ensure_epoch_in_progress_state(deps.storage)?; + // push the interval event let interval_event = PendingIntervalEventKind::UpdateRewardingParams { update: updated_params, @@ -366,7 +393,236 @@ pub mod tests { ZERO_PERFORMANCE_VALUE, }; use mixnet_contract_common::helpers::compare_decimals; - use mixnet_contract_common::RewardedSetNodeStatus; + use mixnet_contract_common::{EpochStatus, RewardedSetNodeStatus}; + + #[cfg(test)] + mod epoch_state_is_correctly_updated { + use super::*; + use mixnet_contract_common::EpochState; + + #[test] + fn when_target_mixnode_unbonded() { + let mut test = TestSetup::new(); + let mix_id_unbonded = test.add_dummy_mixnode("mix-owner-unbonded", None); + let mix_id_unbonded_leftover = + test.add_dummy_mixnode("mix-owner-unbonded-leftover", None); + let mix_id_never_existed = 42; + test.skip_to_next_epoch_end(); + test.force_change_rewarded_set(vec![ + mix_id_unbonded, + mix_id_unbonded_leftover, + mix_id_never_existed, + ]); + test.start_epoch_transition(); + + let env = test.env(); + + // note: we don't have to test for cases where `is_unbonding` is set to true on a mixnode + // since before performing the nym-api should clear out the event queue + + // manually adjust delegation info as to indicate the rewarding information shouldnt get removed + let mut rewarding_details = storage::MIXNODE_REWARDING + .load(test.deps().storage, mix_id_unbonded_leftover) + .unwrap(); + rewarding_details.delegates = Decimal::raw(12345); + rewarding_details.unique_delegations = 1; + storage::MIXNODE_REWARDING + .save( + test.deps_mut().storage, + mix_id_unbonded_leftover, + &rewarding_details, + ) + .unwrap(); + pending_events::unbond_mixnode(test.deps_mut(), &env, 123, mix_id_unbonded) + .unwrap(); + + pending_events::unbond_mixnode( + test.deps_mut(), + &env, + 123, + mix_id_unbonded_leftover, + ) + .unwrap(); + + let env = test.env(); + let sender = test.rewarding_validator(); + let performance = test_helpers::performance(100.0); + + try_reward_mixnode( + test.deps_mut(), + env.clone(), + sender.clone(), + mix_id_unbonded, + performance, + ) + .unwrap(); + assert_eq!( + EpochState::Rewarding { + last_rewarded: mix_id_unbonded, + final_node_id: mix_id_never_existed + }, + interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state + ); + + try_reward_mixnode( + test.deps_mut(), + env.clone(), + sender.clone(), + mix_id_unbonded_leftover, + performance, + ) + .unwrap(); + assert_eq!( + EpochState::Rewarding { + last_rewarded: mix_id_unbonded_leftover, + final_node_id: mix_id_never_existed + }, + interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state + ); + + try_reward_mixnode( + test.deps_mut(), + env.clone(), + sender.clone(), + mix_id_never_existed, + performance, + ) + .unwrap(); + assert_eq!( + EpochState::ReconcilingEvents, + interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state + ); + } + + #[test] + fn when_target_mixnode_has_zero_performance() { + let mut test = TestSetup::new(); + let mix_id = test.add_dummy_mixnode("mix-owner", None); + + test.skip_to_next_epoch_end(); + test.force_change_rewarded_set(vec![mix_id]); + test.start_epoch_transition(); + let zero_performance = test_helpers::performance(0.); + let env = test.env(); + let sender = test.rewarding_validator(); + + try_reward_mixnode(test.deps_mut(), env, sender, mix_id, zero_performance).unwrap(); + assert_eq!( + EpochState::ReconcilingEvents, + interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state + ); + } + + #[test] + fn when_theres_only_one_node_to_reward() { + let mut test = TestSetup::new(); + let mix_id = test.add_dummy_mixnode("mix-owner", None); + + test.skip_to_next_epoch_end(); + test.force_change_rewarded_set(vec![mix_id]); + test.start_epoch_transition(); + let performance = test_helpers::performance(100.0); + let env = test.env(); + let sender = test.rewarding_validator(); + + try_reward_mixnode(test.deps_mut(), env, sender, mix_id, performance).unwrap(); + assert_eq!( + EpochState::ReconcilingEvents, + interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state + ); + } + + #[test] + fn when_theres_multiple_nodes_to_reward() { + let mut test = TestSetup::new(); + + let mut ids = Vec::new(); + for i in 0..100 { + let mix_id = test.add_dummy_mixnode(&format!("mix-owner{i}"), None); + ids.push(mix_id); + } + + test.skip_to_next_epoch_end(); + test.force_change_rewarded_set(ids.clone()); + test.start_epoch_transition(); + let performance = test_helpers::performance(100.0); + let env = test.env(); + let sender = test.rewarding_validator(); + + for mix_id in ids { + try_reward_mixnode( + test.deps_mut(), + env.clone(), + sender.clone(), + mix_id, + performance, + ) + .unwrap(); + + let current_state = interval_storage::current_epoch_status(test.deps().storage) + .unwrap() + .state; + if mix_id == 100 { + assert_eq!(EpochState::ReconcilingEvents, current_state) + } else { + assert_eq!( + EpochState::Rewarding { + last_rewarded: mix_id, + final_node_id: 100 + }, + current_state + ) + } + } + } + } + + #[test] + fn can_only_be_performed_if_in_rewarding_state() { + let bad_states = vec![ + EpochState::InProgress, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let rewarding_validator = test.rewarding_validator(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + test.skip_to_current_epoch_end(); + test.force_change_rewarded_set(vec![1, 2, 3]); + let env = test.env(); + + let res = try_reward_mixnode( + test.deps_mut(), + env, + rewarding_validator, + 1, + test_helpers::performance(100.), + ); + assert_eq!( + res, + Err(MixnetContractError::UnexpectedNonRewardingEpochState { + current_state: bad_state + }) + ); + } + } #[test] fn can_only_be_performed_by_specified_rewarding_validator() { @@ -377,7 +633,8 @@ pub mod tests { // skip time to when the following epoch is over (since mixnodes are not eligible for rewarding // in the same epoch they're bonded and we need the rewarding epoch to be over) test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); + test.start_epoch_transition(); let performance = test_helpers::performance(100.); let env = test.env(); @@ -399,16 +656,17 @@ pub mod tests { let mix_id_unbonded_leftover = test.add_dummy_mixnode("mix-owner-unbonded-leftover", None); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![ - mix_id_never_existed, + test.force_change_rewarded_set(vec![ mix_id_unbonded, mix_id_unbonded_leftover, + mix_id_never_existed, ]); + test.start_epoch_transition(); let env = test.env(); // note: we don't have to test for cases where `is_unbonding` is set to true on a mixnode - // since before performing the validator-api should clear out the event queue + // since before performing the nym-api should clear out the event queue // manually adjust delegation info as to indicate the rewarding information shouldnt get removed let mut rewarding_details = storage::MIXNODE_REWARDING @@ -433,9 +691,9 @@ pub mod tests { let performance = test_helpers::performance(100.0); for &mix_id in &[ - mix_id_never_existed, mix_id_unbonded, mix_id_unbonded_leftover, + mix_id_never_existed, ] { let res = try_reward_mixnode( test.deps_mut(), @@ -464,7 +722,7 @@ pub mod tests { // node is in the active set BUT the current epoch has just begun test.skip_to_next_epoch(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); let performance = test_helpers::performance(100.); let env = test.env(); @@ -476,6 +734,7 @@ pub mod tests { // epoch is over (sanity check) test.skip_to_current_epoch_end(); + test.start_epoch_transition(); let env = test.env(); let res = try_reward_mixnode(test.deps_mut(), env, sender, mix_id, performance); assert!(res.is_ok()); @@ -508,6 +767,16 @@ pub mod tests { ) .unwrap(); + // actually add one more dummy node with high id so we wouldn't go into the next state + interval_storage::REWARDED_SET + .save( + test.deps_mut().storage, + 9001, + &RewardedSetNodeStatus::Standby, + ) + .unwrap(); + test.start_epoch_transition(); + let performance = test_helpers::performance(100.); let env = test.env(); let res_active = try_reward_mixnode( @@ -541,7 +810,8 @@ pub mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id, 42]); + test.start_epoch_transition(); let performance = test_helpers::performance(100.); let env = test.env(); let sender = test.rewarding_validator(); @@ -565,6 +835,8 @@ pub mod tests { // in the following epoch we're good again test.skip_to_next_epoch_end(); + test.start_epoch_transition(); + let env = test.env(); let res = try_reward_mixnode(test.deps_mut(), env, sender, mix_id, performance); assert!(res.is_ok()); @@ -576,7 +848,8 @@ pub mod tests { let mix_id = test.add_dummy_mixnode("mix-owner", None); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id, 42]); + test.start_epoch_transition(); let zero_performance = test_helpers::performance(0.); let performance = test_helpers::performance(100.0); let env = test.env(); @@ -614,6 +887,8 @@ pub mod tests { // but in the next epoch, as always, we're good again test.skip_to_next_epoch_end(); + test.start_epoch_transition(); + let env = test.env(); let res = try_reward_mixnode(test.deps_mut(), env, sender, mix_id, performance).unwrap(); @@ -642,7 +917,8 @@ pub mod tests { let mix_id3 = test.add_dummy_mixnode("mix-owner3", None); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1, mix_id2, mix_id3]); + test.force_change_rewarded_set(vec![mix_id1, mix_id2, mix_id3]); + test.start_epoch_transition(); let performance = test_helpers::performance(98.0); let env = test.env(); let sender = test.rewarding_validator(); @@ -726,7 +1002,8 @@ pub mod tests { let mix_id3 = test.add_dummy_mixnode("mix-owner3", Some(operator3)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1, mix_id2, mix_id3]); + test.start_epoch_transition(); + test.force_change_rewarded_set(vec![mix_id1, mix_id2, mix_id3]); let performance = test_helpers::performance(98.0); test.add_immediate_delegation("delegator1", Uint128::new(100_000_000), mix_id2); @@ -735,8 +1012,12 @@ pub mod tests { test.add_immediate_delegation("delegator2", Uint128::new(123_456_000), mix_id3); test.add_immediate_delegation("delegator3", Uint128::new(9_100_000_000), mix_id3); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); + // repeat the rewarding the same set of delegates for few epochs for _ in 0..10 { + test.start_epoch_transition(); for &mix_id in &[mix_id1, mix_id2, mix_id3] { let mut sim = test.instantiate_simulator(mix_id); let dist = test.reward_with_distribution(mix_id, performance); @@ -747,6 +1028,8 @@ pub mod tests { let sim_res = sim.simulate_epoch_single_node(node_params).unwrap(); assert_eq!(sim_res, dist); } + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); test.skip_to_next_epoch_end(); } @@ -759,8 +1042,12 @@ pub mod tests { test.add_immediate_delegation("delegator5", Uint128::new(123_000_000), mix_id3); test.add_immediate_delegation("delegator6", Uint128::new(456_000_000), mix_id3); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); + let performance = test_helpers::performance(12.3); for _ in 0..10 { + test.start_epoch_transition(); for &mix_id in &[mix_id1, mix_id2, mix_id3] { let mut sim = test.instantiate_simulator(mix_id); let dist = test.reward_with_distribution(mix_id, performance); @@ -771,6 +1058,8 @@ pub mod tests { let sim_res = sim.simulate_epoch_single_node(node_params).unwrap(); assert_eq!(sim_res, dist); } + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); test.skip_to_next_epoch_end(); } } @@ -787,7 +1076,7 @@ pub mod tests { let mix_id2 = test.add_dummy_mixnode("mix-owner2", Some(operator2)); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1, mix_id2]); + test.force_change_rewarded_set(vec![mix_id1, mix_id2]); let performance = test_helpers::performance(98.0); test.add_immediate_delegation("delegator1", Uint128::new(100_000_000), mix_id1); @@ -800,6 +1089,8 @@ pub mod tests { let del21 = test.delegation(mix_id2, "delegator1", &None); for _ in 0..10 { + test.start_epoch_transition(); + // we know from the previous tests that actual rewarding distribution matches the simulator let mut sim1 = test.instantiate_simulator(mix_id1); let mut sim2 = test.instantiate_simulator(mix_id2); @@ -933,6 +1224,8 @@ pub mod tests { assert_eq!(dist2.delegates, computed_del21_reward); test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } // add more delegations and check few more epochs (so that the delegations would start from non-default unit delegation value) @@ -943,6 +1236,8 @@ pub mod tests { let del23 = test.delegation(mix_id2, "delegator3", &None); for _ in 0..10 { + test.start_epoch_transition(); + // we know from the previous tests that actual rewarding distribution matches the simulator let mut sim1 = test.instantiate_simulator(mix_id1); let mut sim2 = test.instantiate_simulator(mix_id2); @@ -1096,6 +1391,8 @@ pub mod tests { ); test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } } } @@ -1104,8 +1401,9 @@ pub mod tests { mod withdrawing_delegator_reward { use super::*; use crate::interval::pending_events; + use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup}; - use cosmwasm_std::{BankMsg, CosmosMsg, Decimal, Uint128}; + use cosmwasm_std::{coin, BankMsg, CosmosMsg, Decimal, Uint128}; use mixnet_contract_common::rewarding::helpers::truncate_reward_amount; #[test] @@ -1131,7 +1429,8 @@ pub mod tests { // perform some rewarding so that we'd have non-zero rewards test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1, mix_id2]); + test.force_change_rewarded_set(vec![mix_id1, mix_id2]); + test.start_epoch_transition(); test.reward_with_distribution(mix_id1, test_helpers::performance(100.0)); test.reward_with_distribution(mix_id2, test_helpers::performance(100.0)); @@ -1178,7 +1477,8 @@ pub mod tests { // reward mix1, but don't reward mix2 test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1, low_stake_id]); + test.force_change_rewarded_set(vec![mix_id1, low_stake_id]); + test.start_epoch_transition(); test.reward_with_distribution(mix_id1, test_helpers::performance(100.0)); test.reward_with_distribution(low_stake_id, test_helpers::performance(100.0)); @@ -1216,14 +1516,18 @@ pub mod tests { let performance = test_helpers::performance(100.0); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_unbonding, mix_id_unbonded_leftover]); + test.force_change_rewarded_set(vec![mix_id_unbonding, mix_id_unbonded_leftover]); // go through few rewarding cycles before unbonding nodes (partially or fully) for _ in 0..10 { + test.start_epoch_transition(); + test.reward_with_distribution(mix_id_unbonding, performance); test.reward_with_distribution(mix_id_unbonded_leftover, performance); test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } // start unbonding the first node and fully unbond the other @@ -1291,12 +1595,13 @@ pub mod tests { let performance = test_helpers::performance(100.0); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_single, mix_id_quad]); + test.force_change_rewarded_set(vec![mix_id_single, mix_id_quad]); // accumulate some rewards let mut accumulated_single = Decimal::zero(); let mut accumulated_quad = Decimal::zero(); for _ in 0..10 { + test.start_epoch_transition(); let dist = test.reward_with_distribution(mix_id_single, performance); // sanity check to make sure test is actually doing what it's supposed to be doing assert!(!dist.delegates.is_zero()); @@ -1306,6 +1611,8 @@ pub mod tests { accumulated_quad += dist.delegates; test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } let before = test.read_delegation(mix_id_single, delegator1, None); @@ -1361,9 +1668,13 @@ pub mod tests { // accumulate some more for _ in 0..10 { + test.start_epoch_transition(); + let dist = test.reward_with_distribution(mix_id_quad, performance); accumulated_quad += dist.delegates; test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } let before1_new = test.read_delegation(mix_id_quad, delegator1, None); @@ -1422,14 +1733,58 @@ pub mod tests { let accumulated_actual = truncate_reward_amount(accumulated_quad); assert_eq_with_leeway(total_claimed, accumulated_actual, Uint128::new(6)); } + + #[test] + fn fails_for_illegal_proxy() { + let test = TestSetup::new(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let mut test = TestSetup::new(); + let mix_id = + test.add_dummy_mixnode("mix-owner1", Some(Uint128::new(1_000_000_000_000))); + + let delegator = "delegator"; + + test.add_immediate_delegation_with_illegal_proxy( + delegator, + 100_000_000u128, + mix_id, + illegal_proxy.clone(), + ); + + // reward the node + test.skip_to_next_epoch_end(); + test.force_change_rewarded_set(vec![mix_id]); + test.start_epoch_transition(); + test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + + let res = try_withdraw_delegator_reward_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), + mix_id, + delegator.to_string(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } } #[cfg(test)] mod withdrawing_operator_reward { use super::*; use crate::interval::pending_events; + use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::test_helpers::TestSetup; - use cosmwasm_std::{BankMsg, CosmosMsg, Uint128}; + use cosmwasm_std::{coin, BankMsg, CosmosMsg, Uint128}; #[test] fn can_only_be_done_if_bond_exists() { @@ -1440,7 +1795,8 @@ pub mod tests { let sender = mock_info("random-guy", &[]); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id]); + test.force_change_rewarded_set(vec![mix_id]); + test.start_epoch_transition(); test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); let res = try_withdraw_operator_reward(test.deps_mut(), sender.clone()); @@ -1466,7 +1822,8 @@ pub mod tests { // reward mix1, but don't reward mix2 test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id1]); + test.force_change_rewarded_set(vec![mix_id1]); + test.start_epoch_transition(); test.reward_with_distribution(mix_id1, test_helpers::performance(100.0)); let res1 = try_withdraw_operator_reward(test.deps_mut(), sender1).unwrap(); @@ -1498,14 +1855,17 @@ pub mod tests { let performance = test_helpers::performance(100.0); test.skip_to_next_epoch_end(); - test.update_rewarded_set(vec![mix_id_unbonding, mix_id_unbonded_leftover]); + test.force_change_rewarded_set(vec![mix_id_unbonding, mix_id_unbonded_leftover]); // go through few rewarding cycles before unbonding nodes (partially or fully) for _ in 0..10 { + test.start_epoch_transition(); test.reward_with_distribution(mix_id_unbonding, performance); test.reward_with_distribution(mix_id_unbonded_leftover, performance); test.skip_to_next_epoch_end(); + // bypass proper epoch progression and force change the state + test.set_epoch_in_progress_state(); } // start unbonding the first node and fully unbond the other @@ -1537,12 +1897,89 @@ pub mod tests { }) ); } + + #[test] + fn fails_for_illegal_proxy() { + let mut test = TestSetup::new(); + + let illegal_proxy = Addr::unchecked("not-vesting-contract"); + let vesting_contract = test.vesting_contract(); + + let owner = "mix-owner1"; + let mix_id = test.add_dummy_mixnode_with_illegal_proxy( + owner, + Some(Uint128::new(1_000_000_000_000)), + illegal_proxy.clone(), + ); + + // reward the node + test.skip_to_next_epoch_end(); + test.force_change_rewarded_set(vec![mix_id]); + test.start_epoch_transition(); + test.reward_with_distribution(mix_id, test_helpers::performance(100.0)); + + let res = try_withdraw_operator_reward_on_behalf( + test.deps_mut(), + mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]), + owner.to_string(), + ) + .unwrap_err(); + + assert_eq!( + res, + MixnetContractError::SenderIsNotVestingContract { + received: illegal_proxy, + vesting_contract + } + ) + } } #[cfg(test)] mod updating_active_set { use super::*; use crate::support::tests::test_helpers::TestSetup; + use mixnet_contract_common::{EpochState, EpochStatus}; + + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let owner = test.owner(); + let env = test.env(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + let res = try_update_active_set_size( + test.deps_mut(), + env.clone(), + owner.clone(), + 100, + false, + ); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + + let res_forced = + try_update_active_set_size(test.deps_mut(), env.clone(), owner, 100, true); + assert!(res_forced.is_ok()) + } + } #[test] fn can_only_be_done_by_contract_owner() { @@ -1706,6 +2143,62 @@ pub mod tests { use super::*; use crate::support::tests::test_helpers::{assert_decimals, TestSetup}; use cosmwasm_std::Decimal; + use mixnet_contract_common::{EpochState, EpochStatus}; + + #[test] + fn cant_be_performed_if_epoch_transition_is_in_progress_unless_forced() { + let bad_states = vec![ + EpochState::Rewarding { + last_rewarded: 0, + final_node_id: 0, + }, + EpochState::ReconcilingEvents, + EpochState::AdvancingEpoch, + ]; + + let update = IntervalRewardingParamsUpdate { + reward_pool: None, + staking_supply: None, + staking_supply_scale_factor: None, + sybil_resistance_percent: None, + active_set_work_factor: None, + interval_pool_emission: None, + rewarded_set_size: Some(123), + }; + + for bad_state in bad_states { + let mut test = TestSetup::new(); + let owner = test.owner(); + let env = test.env(); + + let mut status = EpochStatus::new(test.rewarding_validator().sender); + status.state = bad_state; + + interval_storage::save_current_epoch_status(test.deps_mut().storage, &status) + .unwrap(); + + let res = try_update_rewarding_params( + test.deps_mut(), + env.clone(), + owner.clone(), + update.clone(), + false, + ); + assert!(matches!( + res, + Err(MixnetContractError::EpochAdvancementInProgress { .. }) + )); + + let res_forced = try_update_rewarding_params( + test.deps_mut(), + env.clone(), + owner, + update.clone(), + true, + ); + assert!(res_forced.is_ok()) + } + } #[test] fn can_only_be_done_by_contract_owner() { diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs index ee47b48f3a..fe13150ec3 100644 --- a/contracts/mixnet/src/support/helpers.rs +++ b/contracts/mixnet/src/support/helpers.rs @@ -1,11 +1,15 @@ -// Copyright 2021-2022 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::gateways::storage as gateways_storage; +use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::mixnodes::storage as mixnodes_storage; -use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, Deps, Response, Storage}; +use cosmwasm_std::{ + wasm_execute, Addr, BankMsg, Coin, CosmosMsg, Deps, MessageInfo, Response, Storage, +}; use mixnet_contract_common::error::MixnetContractError; -use mixnet_contract_common::{IdentityKeyRef, MixNodeBond}; +use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond}; +use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; // helper trait to attach `Msg` to a response if it's provided pub(crate) trait AttachOptionalMessage { @@ -22,6 +26,99 @@ impl AttachOptionalMessage for Response { } } +// another helper trait to remove some duplicate code and consolidate comments regarding +// possible epoch progression halting behaviour +pub(crate) trait VestingTracking +where + Self: Sized, +{ + fn maybe_add_track_vesting_undelegation_message( + self, + storage: &dyn Storage, + proxy: Option, + owner: String, + mix_id: MixId, + amount: Coin, + ) -> Result; + + fn maybe_add_track_vesting_unbond_mixnode_message( + self, + storage: &dyn Storage, + proxy: Option, + owner: String, + amount: Coin, + ) -> Result; +} + +impl VestingTracking for Response { + fn maybe_add_track_vesting_undelegation_message( + self, + storage: &dyn Storage, + proxy: Option, + owner: String, + mix_id: MixId, + amount: Coin, + ) -> Result { + // if there's a proxy set (i.e. the vesting contract), send the track message + if let Some(proxy) = proxy { + let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?; + + // Note: this can INTENTIONALLY cause epoch progression halt if the proxy is not the vesting contract + // But this is fine, since this situation should have NEVER occurred in the first place + // (as all 'on_behalf' methods, including 'DelegateToMixnodeOnBehalf' that got us here, + // explicitly require the proxy to be the vesting contract) + // 'fixing' it would require manually inspecting the problematic event, investigating + // it's cause and manually (presumably via migration) clearing it. + if proxy != vesting_contract { + return Err(MixnetContractError::ProxyIsNotVestingContract { + received: proxy, + vesting_contract, + }); + } + + let msg = VestingContractExecuteMsg::TrackUndelegation { + owner, + mix_id, + amount, + }; + + let track_undelegate_message = wasm_execute(proxy, &msg, vec![])?; + Ok(self.add_message(track_undelegate_message)) + } else { + // there's no proxy so nothing to do + Ok(self) + } + } + + fn maybe_add_track_vesting_unbond_mixnode_message( + self, + storage: &dyn Storage, + proxy: Option, + owner: String, + amount: Coin, + ) -> Result { + // if there's a proxy set (i.e. the vesting contract), send the track message + if let Some(proxy) = proxy { + let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?; + + // exactly the same possible halting behaviour as in `maybe_add_track_vesting_undelegation_message`. + if proxy != vesting_contract { + return Err(MixnetContractError::ProxyIsNotVestingContract { + received: proxy, + vesting_contract, + }); + } + + let msg = VestingContractExecuteMsg::TrackUnbondMixnode { owner, amount }; + let track_unbond_message = wasm_execute(proxy, &msg, vec![])?; + Ok(self.add_message(track_unbond_message)) + } else { + // there's no proxy so nothing to do + Ok(self) + } + } +} + // pub fn debug_with_visibility>(api: &dyn Api, msg: S) { // api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into())); // } @@ -117,16 +214,80 @@ pub(crate) fn validate_delegation_stake( Ok(delegation.pop().unwrap()) } -pub(crate) fn ensure_is_authorized( - sender: Addr, +pub(crate) fn ensure_epoch_in_progress_state( storage: &dyn Storage, ) -> Result<(), MixnetContractError> { - if sender != crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)? { + let epoch_status = crate::interval::storage::current_epoch_status(storage)?; + if !matches!(epoch_status.state, EpochState::InProgress) { + return Err(MixnetContractError::EpochAdvancementInProgress { + current_state: epoch_status.state, + }); + } + Ok(()) +} + +// pub(crate) fn ensure_mix_rewarding_state(storage: &dyn Storage) -> Result<(), MixnetContractError> { +// let epoch_status = crate::interval::storage::current_epoch_status(storage)?; +// if !matches!(epoch_status.state, EpochState::Rewarding { .. }) { +// return Err(MixnetContractError::EpochNotInMixRewardingState { +// current_state: epoch_status.state, +// }); +// } +// Ok(()) +// } +// +// pub(crate) fn ensure_event_reconciliation_state( +// storage: &dyn Storage, +// ) -> Result<(), MixnetContractError> { +// let epoch_status = crate::interval::storage::current_epoch_status(storage)?; +// if !matches!(epoch_status.state, EpochState::ReconcilingEvents) { +// return Err(MixnetContractError::EpochNotInEventReconciliationState { +// current_state: epoch_status.state, +// }); +// } +// Ok(()) +// } +// +// pub(crate) fn ensure_epoch_advancement_state( +// storage: &dyn Storage, +// ) -> Result<(), MixnetContractError> { +// let epoch_status = crate::interval::storage::current_epoch_status(storage)?; +// if !matches!(epoch_status.state, EpochState::AdvancingEpoch) { +// return Err(MixnetContractError::EpochNotInAdvancementState { +// current_state: epoch_status.state, +// }); +// } +// Ok(()) +// } + +pub(crate) fn ensure_is_authorized( + sender: &Addr, + storage: &dyn Storage, +) -> Result<(), MixnetContractError> { + if sender != &crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)? { return Err(MixnetContractError::Unauthorized); } Ok(()) } +pub(crate) fn ensure_can_advance_epoch( + sender: &Addr, + storage: &dyn Storage, +) -> Result { + let epoch_status = crate::interval::storage::current_epoch_status(storage)?; + if sender != &epoch_status.being_advanced_by { + // well, we know we're going to throw an error now, + // but we might as well also check if we're even a validator + // to return a possibly better error message + ensure_is_authorized(sender, storage)?; + return Err(MixnetContractError::RewardingValidatorMismatch { + current_validator: sender.clone(), + chosen_validator: epoch_status.being_advanced_by, + }); + } + Ok(epoch_status) +} + pub(crate) fn ensure_is_owner( sender: Addr, storage: &dyn Storage, @@ -158,6 +319,22 @@ pub(crate) fn ensure_proxy_match( Ok(()) } +pub(crate) fn ensure_sent_by_vesting_contract( + info: &MessageInfo, + storage: &dyn Storage, +) -> Result<(), MixnetContractError> { + let vesting_contract_address = + crate::mixnet_contract_settings::storage::vesting_contract_address(storage)?; + if info.sender != vesting_contract_address { + Err(MixnetContractError::SenderIsNotVestingContract { + received: info.sender.clone(), + vesting_contract: vesting_contract_address, + }) + } else { + Ok(()) + } +} + pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractError> { if bond.is_unbonding { return Err(MixnetContractError::MixnodeIsUnbonding { @@ -170,8 +347,8 @@ pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractErro // check if the target address has already bonded a mixnode or gateway, // in either case, return an appropriate error pub(crate) fn ensure_no_existing_bond( - storage: &dyn Storage, sender: &Addr, + storage: &dyn Storage, ) -> Result<(), MixnetContractError> { if mixnodes_storage::mixnode_bonds() .idx @@ -255,7 +432,7 @@ pub(crate) fn validate_signature( mod tests { use super::*; use cosmwasm_std::testing::mock_dependencies; - use crypto::asymmetric::identity; + use nym_crypto::asymmetric::identity; use rand_chacha::rand_core::SeedableRng; #[test] diff --git a/contracts/mixnet/src/support/tests/messages.rs b/contracts/mixnet/src/support/tests/messages.rs index f3593b3aca..bf9ed53895 100644 --- a/contracts/mixnet/src/support/tests/messages.rs +++ b/contracts/mixnet/src/support/tests/messages.rs @@ -7,7 +7,7 @@ pub(crate) fn valid_bond_gateway_msg( mut rng: impl RngCore + CryptoRng, sender: &str, ) -> (ExecuteMsg, IdentityKey) { - let keypair = crypto::asymmetric::identity::KeyPair::new(&mut rng); + let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng); let owner_signature = keypair .private_key() .sign(sender.as_bytes()) diff --git a/contracts/mixnet/src/support/tests/mod.rs b/contracts/mixnet/src/support/tests/mod.rs index 9f03f9c93c..4e22de893f 100644 --- a/contracts/mixnet/src/support/tests/mod.rs +++ b/contracts/mixnet/src/support/tests/mod.rs @@ -15,9 +15,11 @@ pub mod test_helpers { use crate::delegations::queries::query_mixnode_delegations_paged; use crate::delegations::storage as delegations_storage; use crate::delegations::transactions::try_delegate_to_mixnode; - use crate::gateways::transactions::try_add_gateway; + use crate::families::transactions::{try_create_family, try_join_family}; + use crate::gateways::storage as gateways_storage; + use crate::gateways::transactions::{try_add_gateway, try_add_gateway_on_behalf}; use crate::interval::transactions::{ - perform_pending_epoch_actions, perform_pending_interval_actions, + perform_pending_epoch_actions, perform_pending_interval_actions, try_begin_epoch_transition, }; use crate::interval::{pending_events, storage as interval_storage}; use crate::mixnet_contract_settings::storage as mixnet_params_storage; @@ -25,6 +27,7 @@ pub mod test_helpers { minimum_mixnode_pledge, rewarding_denom, rewarding_validator_address, }; use crate::mixnodes::storage as mixnodes_storage; + use crate::mixnodes::storage::mixnode_bonds; use crate::mixnodes::transactions::{ try_add_mixnode, try_add_mixnode_on_behalf, try_remove_mixnode, }; @@ -40,13 +43,12 @@ pub mod test_helpers { use cosmwasm_std::testing::mock_info; use cosmwasm_std::testing::MockApi; use cosmwasm_std::testing::MockQuerier; - use cosmwasm_std::{coin, Addr, BankMsg, CosmosMsg, Storage}; + use cosmwasm_std::{coin, Addr, Api, BankMsg, CosmosMsg, Storage}; use cosmwasm_std::{Coin, Order}; use cosmwasm_std::{Decimal, Empty, MemoryStorage}; use cosmwasm_std::{Deps, OwnedDeps}; use cosmwasm_std::{DepsMut, MessageInfo}; use cosmwasm_std::{Env, Response, Timestamp, Uint128}; - use crypto::asymmetric::identity::KeyPair; use mixnet_contract_common::events::{ may_find_attribute, MixnetEventType, DELEGATES_REWARD_KEY, OPERATOR_REWARD_KEY, }; @@ -57,9 +59,11 @@ pub mod test_helpers { use mixnet_contract_common::rewarding::simulator::Simulator; use mixnet_contract_common::rewarding::RewardDistribution; use mixnet_contract_common::{ - Delegation, Gateway, InitialRewardingParams, InstantiateMsg, Interval, MixId, MixNode, - Percent, RewardedSetNodeStatus, + Delegation, EpochState, EpochStatus, Gateway, IdentityKey, InitialRewardingParams, + InstantiateMsg, Interval, MixId, MixNode, MixNodeBond, Percent, RewardedSetNodeStatus, }; + use nym_crypto::asymmetric::identity; + use nym_crypto::asymmetric::identity::KeyPair; use rand_chacha::rand_core::{CryptoRng, RngCore, SeedableRng}; use rand_chacha::ChaCha20Rng; use std::time::Duration; @@ -157,6 +161,42 @@ pub mod test_helpers { .collect::>() } + #[allow(unused)] + pub fn join_family( + &mut self, + member: &str, + member_keys: &identity::KeyPair, + head_keys: &identity::KeyPair, + ) { + let identity_signature = member_keys.private_key().sign_text(member); + let join_signature = head_keys + .private_key() + .sign(&member_keys.public_key().to_bytes()) + .to_base58_string(); + let head_identity = head_keys.public_key().to_base58_string(); + + try_join_family( + self.deps_mut(), + mock_info(member, &[]), + Some(identity_signature), + join_signature, + head_identity, + ) + .unwrap(); + } + + pub fn create_dummy_mixnode_with_new_family( + &mut self, + head: &str, + label: &str, + ) -> (MixId, identity::KeyPair) { + let (mix_id, keys) = self.add_dummy_mixnode_with_keypair(head, None); + let sig = keys.private_key().sign_text(head); + + try_create_family(self.deps_mut(), mock_info(head, &[]), sig, label).unwrap(); + (mix_id, keys) + } + pub fn add_dummy_mixnode(&mut self, owner: &str, stake: Option) -> MixId { let stake = match stake { Some(amount) => { @@ -170,12 +210,11 @@ pub mod test_helpers { add_mixnode(&mut self.rng, self.deps.as_mut(), env, owner, vec![stake]) } - pub fn add_dummy_mixnode_with_proxy( + pub fn add_dummy_mixnode_with_keypair( &mut self, owner: &str, stake: Option, - proxy: Addr, - ) -> MixId { + ) -> (MixId, identity::KeyPair) { let stake = match stake { Some(amount) => { let denom = rewarding_denom(self.deps().storage).unwrap(); @@ -184,13 +223,15 @@ pub mod test_helpers { None => minimum_mixnode_pledge(self.deps.as_ref().storage).unwrap(), }; - let keypair = crypto::asymmetric::identity::KeyPair::new(&mut self.rng); + let proxy = self.vesting_contract(); + + let keypair = identity::KeyPair::new(&mut self.rng); let owner_signature = keypair .private_key() .sign(owner.as_bytes()) .to_base58_string(); - let legit_sphinx_key = crypto::asymmetric::encryption::KeyPair::new(&mut self.rng); + let legit_sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng); let info = mock_info(proxy.as_str(), &[stake]); let key = keypair.public_key().to_base58_string(); @@ -216,7 +257,98 @@ pub mod test_helpers { .unwrap(); // newly added mixnode gets assigned the current counter + 1 - current_id_counter + 1 + (current_id_counter + 1, keypair) + } + + pub fn add_dummy_mixnode_with_legal_proxy( + &mut self, + owner: &str, + stake: Option, + ) -> MixId { + self.add_dummy_mixnode_with_keypair(owner, stake).0 + } + + pub fn set_illegal_mixnode_proxy(&mut self, mix_id: MixId, proxy: Addr) { + let mut bond_details = mixnodes_storage::mixnode_bonds() + .load(self.deps().storage, mix_id) + .unwrap(); + bond_details.proxy = Some(proxy); + mixnodes_storage::mixnode_bonds() + .save(self.deps_mut().storage, mix_id, &bond_details) + .unwrap(); + } + + pub fn add_dummy_gateway_with_illegal_proxy( + &mut self, + owner: &str, + stake: Option, + proxy: Addr, + ) -> IdentityKey { + let gateway_identity = self.add_dummy_gateway_with_legal_proxy(owner, stake); + self.set_illegal_gateway_proxy(&gateway_identity, proxy); + gateway_identity + } + + pub fn set_illegal_gateway_proxy(&mut self, gateway_id: &str, proxy: Addr) { + let mut gateway = gateways_storage::gateways() + .load(self.deps().storage, gateway_id) + .unwrap(); + gateway.proxy = Some(proxy); + gateways_storage::gateways() + .save(self.deps_mut().storage, gateway_id, &gateway) + .unwrap(); + } + + pub fn add_dummy_gateway_with_legal_proxy( + &mut self, + owner: &str, + stake: Option, + ) -> IdentityKey { + let stake = match stake { + Some(amount) => { + let denom = rewarding_denom(self.deps().storage).unwrap(); + Coin { denom, amount } + } + None => minimum_mixnode_pledge(self.deps.as_ref().storage).unwrap(), + }; + + let proxy = self.vesting_contract(); + + let legit_sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng); + let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut self.rng); + let owner_signature = keypair + .private_key() + .sign(owner.as_bytes()) + .to_base58_string(); + + let env = self.env(); + let info = mock_info(proxy.as_ref(), &[stake]); + + try_add_gateway_on_behalf( + self.deps_mut(), + env, + info, + Gateway { + identity_key: keypair.public_key().to_base58_string(), + sphinx_key: legit_sphinx_key.public_key().to_base58_string(), + ..tests::fixtures::gateway_fixture() + }, + owner.to_string(), + owner_signature, + ) + .unwrap(); + keypair.public_key().to_base58_string() + } + + pub fn add_dummy_mixnode_with_illegal_proxy( + &mut self, + owner: &str, + stake: Option, + proxy: Addr, + ) -> MixId { + let mix_id = self.add_dummy_mixnode_with_legal_proxy(owner, stake); + self.set_illegal_mixnode_proxy(mix_id, proxy); + mix_id } pub fn start_unbonding_mixnode(&mut self, mix_id: MixId) { @@ -263,7 +395,34 @@ pub mod test_helpers { .unwrap(); } - pub fn add_immediate_delegation_with_proxy( + pub fn add_immediate_delegation_with_legal_proxy( + &mut self, + delegator: &str, + amount: impl Into, + target: MixId, + ) { + let denom = rewarding_denom(self.deps().storage).unwrap(); + let amount = Coin { + denom, + amount: amount.into(), + }; + let env = self.env(); + let proxy = self.vesting_contract(); + pending_events::delegate( + self.deps_mut(), + &env, + env.block.height, + Addr::unchecked(delegator), + target, + amount, + Some(proxy), + ) + .unwrap(); + } + + // to set illegal proxy we have to bypass "normal" flow and put the value + // directly into the storage + pub fn add_immediate_delegation_with_illegal_proxy( &mut self, delegator: &str, amount: impl Into, @@ -275,17 +434,41 @@ pub mod test_helpers { denom, amount: amount.into(), }; - let env = self.env(); - pending_events::delegate( - self.deps_mut(), - &env, - env.block.height, - Addr::unchecked(delegator), + + let owner = self.deps.api.addr_validate(delegator).unwrap(); + let storage_key = Delegation::generate_storage_key(target, &owner, Some(&proxy)); + + let mut mix_rewarding = self.mix_rewarding(target); + + let mut stored_delegation_amount = amount; + + if let Some(existing_delegation) = delegations_storage::delegations() + .may_load(&self.deps.storage, storage_key.clone()) + .unwrap() + { + let og_with_reward = mix_rewarding.undelegate(&existing_delegation).unwrap(); + stored_delegation_amount.amount += og_with_reward.amount; + } + + mix_rewarding + .add_base_delegation(stored_delegation_amount.amount) + .unwrap(); + + let delegation = Delegation::new( + owner, target, - amount, + mix_rewarding.total_unit_reward, + stored_delegation_amount, + self.env.block.height, Some(proxy), - ) - .unwrap(); + ); + + delegations_storage::delegations() + .save(&mut self.deps.storage, storage_key, &delegation) + .unwrap(); + rewards_storage::MIXNODE_REWARDING + .save(&mut self.deps.storage, target, &mix_rewarding) + .unwrap(); } #[allow(unused)] @@ -316,6 +499,48 @@ pub mod test_helpers { .unwrap(); } + pub fn start_epoch_transition(&mut self) { + let env = self.env.clone(); + let sender = self.rewarding_validator.clone(); + try_begin_epoch_transition(self.deps_mut(), env, sender).unwrap(); + } + + pub fn set_epoch_in_progress_state(&mut self) { + let being_advanced_by = self.rewarding_validator.sender.clone(); + interval_storage::save_current_epoch_status( + self.deps_mut().storage, + &EpochStatus { + being_advanced_by, + state: EpochState::InProgress, + }, + ) + .unwrap(); + } + + pub fn set_epoch_reconciliation_state(&mut self) { + let being_advanced_by = self.rewarding_validator.sender.clone(); + interval_storage::save_current_epoch_status( + self.deps_mut().storage, + &EpochStatus { + being_advanced_by, + state: EpochState::ReconcilingEvents, + }, + ) + .unwrap(); + } + + pub fn set_epoch_advancement_state(&mut self) { + let being_advanced_by = self.rewarding_validator.sender.clone(); + interval_storage::save_current_epoch_status( + self.deps_mut().storage, + &EpochStatus { + being_advanced_by, + state: EpochState::AdvancingEpoch, + }, + ) + .unwrap(); + } + #[allow(unused)] pub fn pending_operator_reward(&mut self, mix: MixId) -> Decimal { query_pending_mixnode_operator_reward(self.deps(), mix) @@ -376,10 +601,12 @@ pub mod test_helpers { ) } - interval_storage::save_interval(self.deps_mut().storage, &advanced).unwrap() + interval_storage::save_interval(self.deps_mut().storage, &advanced).unwrap(); + // if we're going into next epoch, we're back into in progress + self.set_epoch_in_progress_state(); } - pub fn update_rewarded_set(&mut self, nodes: Vec) { + pub fn force_change_rewarded_set(&mut self, nodes: Vec) { let active_set_size = rewards_storage::REWARDING_PARAMS .load(self.deps().storage) .unwrap() @@ -411,6 +638,20 @@ pub mod test_helpers { .collect::>() } + pub fn reward_with_distribution_with_state_bypass( + &mut self, + mix_id: MixId, + performance: Performance, + ) -> RewardDistribution { + let initial_status = + interval_storage::current_epoch_status(self.deps().storage).unwrap(); + self.start_epoch_transition(); + let res = self.reward_with_distribution(mix_id, performance); + interval_storage::save_current_epoch_status(self.deps_mut().storage, &initial_status) + .unwrap(); + res + } + pub fn reward_with_distribution( &mut self, mix_id: MixId, @@ -463,6 +704,11 @@ pub mod test_helpers { .unwrap() } + #[allow(unused)] + pub fn mix_bond(&self, mix_id: MixId) -> MixNodeBond { + mixnode_bonds().load(self.deps().storage, mix_id).unwrap() + } + pub fn delegation(&self, mix: MixId, owner: &str, proxy: &Option) -> Delegation { read_delegation(self.deps().storage, mix, &Addr::unchecked(owner), proxy).unwrap() } @@ -558,8 +804,8 @@ pub mod test_helpers { mut rng: impl RngCore + CryptoRng, sender: &str, ) -> (MixNode, String, KeyPair) { - let keypair = crypto::asymmetric::identity::KeyPair::new(&mut rng); - let legit_sphinx_key = crypto::asymmetric::encryption::KeyPair::new(&mut rng); + let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng); + let legit_sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut rng); let owner_signature = keypair .private_key() .sign(sender.as_bytes()) @@ -583,8 +829,8 @@ pub mod test_helpers { mut rng: impl RngCore + CryptoRng, sender: &str, ) -> (Gateway, String) { - let keypair = crypto::asymmetric::identity::KeyPair::new(&mut rng); - let legit_sphinx_key = crypto::asymmetric::encryption::KeyPair::new(&mut rng); + let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng); + let legit_sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut rng); let owner_signature = keypair .private_key() .sign(sender.as_bytes()) @@ -734,13 +980,13 @@ pub mod test_helpers { sender: &str, stake: Vec, ) -> MixId { - let keypair = crypto::asymmetric::identity::KeyPair::new(&mut rng); + let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng); let owner_signature = keypair .private_key() .sign(sender.as_bytes()) .to_base58_string(); - let legit_sphinx_key = crypto::asymmetric::encryption::KeyPair::new(&mut rng); + let legit_sphinx_key = nym_crypto::asymmetric::encryption::KeyPair::new(&mut rng); let info = mock_info(sender, &stake); let key = keypair.public_key().to_base58_string(); @@ -775,7 +1021,7 @@ pub mod test_helpers { sender: &str, stake: Vec, ) -> String { - let keypair = crypto::asymmetric::identity::KeyPair::new(&mut rng); + let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng); let owner_signature = keypair .private_key() .sign(sender.as_bytes()) diff --git a/contracts/mixnet/v2-changes.md b/contracts/mixnet/v2-changes.md index ecb290703c..dc09e6784a 100644 --- a/contracts/mixnet/v2-changes.md +++ b/contracts/mixnet/v2-changes.md @@ -898,6 +898,6 @@ As with everything in this module, storage was also completely revamped. The cha ## Final remarks -As mentioned during multiple chats, I think the migration the rest of our codebase is going to be a huge undertaking mostly because of how many aspects of the system this change is affecting. From the top of my head, we'd need to definitely change our `nymd client` (and as a result `validator-api`, `clients`, etc.) and also the vesting contract. +As mentioned during multiple chats, I think the migration the rest of our codebase is going to be a huge undertaking mostly because of how many aspects of the system this change is affecting. From the top of my head, we'd need to definitely change our `nyxd client` (and as a result `validator-api`, `clients`, etc.) and also the vesting contract. With the latter case (and with the current mixnet contract), it's going to be even trickier given that the current contract is already live. We will need to adjust how the values are stored, i.e. mixnodes are now indexed by `NodeId` as opposed to `IdentityKey`. My recommendation would be to create a migration such that it would "cancel" / "return" (you name it) all existing delegations and bonds so that the users would have to make new ones under the new contract. diff --git a/contracts/multisig/cw3-flex-multisig/Cargo.toml b/contracts/multisig/cw3-flex-multisig/Cargo.toml index 8afa71f251..0c45bb149d 100644 --- a/contracts/multisig/cw3-flex-multisig/Cargo.toml +++ b/contracts/multisig/cw3-flex-multisig/Cargo.toml @@ -17,20 +17,20 @@ crate-type = ["cdylib", "rlib"] library = [] [dependencies] -cw-utils = { version = "0.13.4" } -cw2 = { version = "0.13.4" } -cw3 = { version = "0.13.4" } -cw3-fixed-multisig = { version = "0.13.4", features = ["library"] } -cw4 = { version = "0.13.4" } -cw-storage-plus = { version = "0.13.4" } -cosmwasm-std = { version = "1.0.0" } +cw-utils = { workspace = true } +cw2 = { workspace = true } +cw3 = { workspace = true } +cw3-fixed-multisig = { workspace = true, features = ["library"] } +cw4 = { workspace = true } +cw-storage-plus = { workspace = true } +cosmwasm-std = { workspace = true } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.23" } -multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" } +nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } +nym-multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" } [dev-dependencies] cosmwasm-schema = { version = "1.0.0" } cw4-group = { path = "../cw4-group", version = "0.13.4" } -cw-multi-test = { version = "0.13.4" } +cw-multi-test = { workspace = true } diff --git a/contracts/multisig/cw3-flex-multisig/src/contract.rs b/contracts/multisig/cw3-flex-multisig/src/contract.rs index 5a56ea9745..bb24d38774 100644 --- a/contracts/multisig/cw3-flex-multisig/src/contract.rs +++ b/contracts/multisig/cw3-flex-multisig/src/contract.rs @@ -17,9 +17,9 @@ use cw4::{Cw4Contract, MemberChangedHookMsg, MemberDiff}; use cw_storage_plus::Bound; use cw_utils::{maybe_addr, Expiration, ThresholdResponse}; -use crate::error::ContractError; use crate::state::{Config, CONFIG}; -use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use nym_multisig_contract_common::error::ContractError; +use nym_multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw3-flex-multisig"; @@ -502,7 +502,7 @@ mod tests { // uploads code and returns address of group contract fn instantiate_group(app: &mut App, members: Vec) -> Addr { let group_id = app.store_code(contract_group()); - let msg = cw4_group::msg::InstantiateMsg { + let msg = nym_group_contract_common::msg::InstantiateMsg { admin: Some(OWNER.into()), members, }; @@ -1410,7 +1410,7 @@ mod tests { // adds NEWBIE with 2 power -> with snapshot, invalid vote // removes VOTER3 -> with snapshot, can vote on proposal let newbie: &str = "newbie"; - let update_msg = cw4_group::msg::ExecuteMsg::UpdateMembers { + let update_msg = nym_group_contract_common::msg::ExecuteMsg::UpdateMembers { remove: vec![VOTER3.into()], add: vec![member(VOTER2, 21), member(newbie, 2)], }; @@ -1623,7 +1623,7 @@ mod tests { // admin changes the group (3 -> 0, 2 -> 9, 0 -> 29) - total = 56, require 29 to pass let newbie: &str = "newbie"; - let update_msg = cw4_group::msg::ExecuteMsg::UpdateMembers { + let update_msg = nym_group_contract_common::msg::ExecuteMsg::UpdateMembers { remove: vec![VOTER3.into()], add: vec![member(VOTER2, 9), member(newbie, 29)], }; @@ -1702,7 +1702,7 @@ mod tests { // admin changes the group (3 -> 0, 2 -> 9, 0 -> 28) - total = 55, require 28 to pass let newbie: &str = "newbie"; - let update_msg = cw4_group::msg::ExecuteMsg::UpdateMembers { + let update_msg = nym_group_contract_common::msg::ExecuteMsg::UpdateMembers { remove: vec![VOTER3.into()], add: vec![member(VOTER2, 9), member(newbie, 29)], }; diff --git a/contracts/multisig/cw3-flex-multisig/src/lib.rs b/contracts/multisig/cw3-flex-multisig/src/lib.rs index e544aa55c8..3407c199dd 100644 --- a/contracts/multisig/cw3-flex-multisig/src/lib.rs +++ b/contracts/multisig/cw3-flex-multisig/src/lib.rs @@ -1,5 +1,2 @@ pub mod contract; -pub mod error; pub mod state; - -pub use crate::error::ContractError; diff --git a/contracts/multisig/cw4-group/Cargo.toml b/contracts/multisig/cw4-group/Cargo.toml index 235cfeed70..4605ec5bcc 100644 --- a/contracts/multisig/cw4-group/Cargo.toml +++ b/contracts/multisig/cw4-group/Cargo.toml @@ -24,15 +24,17 @@ crate-type = ["cdylib", "rlib"] library = [] [dependencies] -cw-utils = { version = "0.13.4" } -cw2 = { version = "0.13.4" } -cw4 = { version = "0.13.4" } -cw-controllers = { version = "0.13.4" } -cw-storage-plus = { version = "0.13.4" } -cosmwasm-std = { version = "1.0.0" } +nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } + +cw-utils = { workspace = true } +cw2 = { workspace = true } +cw4 = { workspace = true } +cw-controllers = { workspace = true } +cw-storage-plus = { workspace = true } +cosmwasm-std = { workspace = true } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } [dev-dependencies] -cosmwasm-schema = { version = "1.0.0" } +cosmwasm-schema = { workspace = true } diff --git a/contracts/multisig/cw4-group/src/contract.rs b/contracts/multisig/cw4-group/src/contract.rs index 913d427f6a..543388c6fa 100644 --- a/contracts/multisig/cw4-group/src/contract.rs +++ b/contracts/multisig/cw4-group/src/contract.rs @@ -13,8 +13,8 @@ use cw_storage_plus::Bound; use cw_utils::maybe_addr; use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{ADMIN, HOOKS, MEMBERS, TOTAL}; +use nym_group_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw4-group"; diff --git a/contracts/multisig/cw4-group/src/error.rs b/contracts/multisig/cw4-group/src/error.rs index 82a84fe833..8c88fb2255 100644 --- a/contracts/multisig/cw4-group/src/error.rs +++ b/contracts/multisig/cw4-group/src/error.rs @@ -5,13 +5,13 @@ use cw_controllers::{AdminError, HookError}; #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("{0}")] + #[error(transparent)] Std(#[from] StdError), - #[error("{0}")] + #[error(transparent)] Hook(#[from] HookError), - #[error("{0}")] + #[error(transparent)] Admin(#[from] AdminError), #[error("Unauthorized")] diff --git a/contracts/multisig/cw4-group/src/helpers.rs b/contracts/multisig/cw4-group/src/helpers.rs index add7451a43..eae8a2434e 100644 --- a/contracts/multisig/cw4-group/src/helpers.rs +++ b/contracts/multisig/cw4-group/src/helpers.rs @@ -5,7 +5,7 @@ use std::ops::Deref; use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg}; use cw4::{Cw4Contract, Member}; -use crate::msg::ExecuteMsg; +use nym_group_contract_common::msg::ExecuteMsg; /// Cw4GroupContract is a wrapper around Cw4Contract that provides a lot of helpers /// for working with cw4-group contracts. diff --git a/contracts/multisig/cw4-group/src/lib.rs b/contracts/multisig/cw4-group/src/lib.rs index 98208b782f..3870baf04b 100644 --- a/contracts/multisig/cw4-group/src/lib.rs +++ b/contracts/multisig/cw4-group/src/lib.rs @@ -1,7 +1,6 @@ pub mod contract; pub mod error; pub mod helpers; -pub mod msg; pub mod state; pub use crate::error::ContractError; diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml index dd8d2966f0..341fdf284f 100644 --- a/contracts/vesting/Cargo.toml +++ b/contracts/vesting/Cargo.toml @@ -1,29 +1,48 @@ [package] -name = "vesting-contract" -version = "1.1.2" -authors = ["Drazen Urch "] -edition = "2021" +name = "nym-vesting-contract" +version = "1.2.0" +description = "Nym vesting contract" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +readme = "README.md" exclude = [ # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. "contract.wasm", "hash.txt", + "artifacts", ] [lib] +name = "vesting_contract" crate-type = ["cdylib", "rlib"] [dependencies] -mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } -contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } -vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } +mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.3.0" } +contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.3.0" } +vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.3.0" } -cosmwasm-std = { version = "1.0.0 "} -cw-storage-plus = { version = "0.13.4", features = ["iterator"] } +cosmwasm-std = { workspace = true } +cosmwasm-derive = { workspace = true } +cw2 = { workspace = true } +cw-storage-plus = { workspace = true, features = ["iterator"] } schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } thiserror = { version = "1.0" } +semver = { version = "1.0.16", default-features = false } + +[dev-dependencies] +rand_chacha = "0.3.1" +base64 = "0.21.0" +hex = "0.4.3" +serde_json = "1.0.66" +cosmwasm-crypto = { workspace = true } [build-dependencies] -vergen = { version = "5", default-features = false, features = ["build", "git", "rustc"] } +vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] } + +[profile.release] +overflow-checks = true diff --git a/contracts/vesting/build.rs b/contracts/vesting/build.rs index 01b3a20dc6..dfffaafd59 100644 --- a/contracts/vesting/build.rs +++ b/contracts/vesting/build.rs @@ -4,5 +4,10 @@ use vergen::{vergen, Config}; fn main() { - vergen(Config::default()).expect("failed to extract build metadata") + let mut config = Config::default(); + if std::env::var("DOCS_RS").is_ok() { + // If we don't have access to git information, such as in a docs.rs build, don't error + *config.git_mut().skip_if_error_mut() = true; + } + vergen(config).expect("failed to extract build metadata") } diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 4a528b41d9..39eff3a86c 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -1,7 +1,6 @@ use crate::errors::ContractError; -use crate::queued_migrations::migrate_to_v2_mixnet_contract; use crate::storage::{ - account_from_address, save_account, BlockTimestampSecs, ADMIN, DELEGATIONS, + account_from_address, save_account, BlockTimestampSecs, ACCOUNTS, ADMIN, DELEGATIONS, MIXNET_CONTRACT_ADDRESS, MIX_DENOM, }; use crate::traits::{ @@ -10,12 +9,13 @@ use crate::traits::{ use crate::vesting::{populate_vesting_periods, Account}; use contracts_common::ContractBuildInformation; use cosmwasm_std::{ - coin, entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Order, - QueryResponse, Response, StdResult, Timestamp, Uint128, + coin, entry_point, to_binary, Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Order, + QueryResponse, Response, StdError, StdResult, Timestamp, Uint128, }; use cw_storage_plus::Bound; use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; use mixnet_contract_common::{Gateway, MixId, MixNode}; +use semver::Version; use vesting_contract_common::events::{ new_ownership_transfer_event, new_periodic_vesting_account_event, new_staking_address_update_event, new_track_gateway_unbond_event, @@ -26,12 +26,30 @@ use vesting_contract_common::messages::{ ExecuteMsg, InitMsg, MigrateMsg, QueryMsg, VestingSpecification, }; use vesting_contract_common::{ - AllDelegationsResponse, DelegationTimesResponse, OriginalVestingResponse, Period, PledgeCap, - PledgeData, VestingDelegation, + AccountVestingCoins, AccountsResponse, AllDelegationsResponse, BaseVestingAccountInfo, + DelegationTimesResponse, OriginalVestingResponse, Period, PledgeCap, PledgeData, + VestingCoinsResponse, VestingDelegation, }; +// version info for migration info +const CONTRACT_NAME: &str = "crate:nym-vesting-contract"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + pub const INITIAL_LOCKED_PLEDGE_CAP: Uint128 = Uint128::new(100_000_000_000); +// as of 01.02.23 the maximum number of delegations anyone has made towards particular mixnode is 12. +// thus restricting it to 25, which is more than double of that, doesn't seem too unreasonable. +// and is going to alleviate the issue of unbounded iteration in `remove_delegations_for_mix` +// that happens upon advancing the current epoch. +// +// However, do note it doesn't necessarily mean that upon reaching this limit it's impossible to perform +// further delegations (towards the same node) +// while this might not be the best workaround, you could remove the existing delegation +// (thus removing all separate entries from the storage, i.e. the `DELEGATIONS` map) +// and re-delegate it with the reclaimed amount (which will include all rewards) +// which will only result in a single key-value being stored. +pub const MAX_PER_MIX_DELEGATIONS: u32 = 25; + /// Instantiate the contract #[entry_point] pub fn instantiate( @@ -40,16 +58,56 @@ pub fn instantiate( info: MessageInfo, msg: InitMsg, ) -> Result { - //! ADMIN is set to the address that instantiated the contract - ADMIN.save(deps.storage, &info.sender.to_string())?; - MIXNET_CONTRACT_ADDRESS.save(deps.storage, &msg.mixnet_contract_address)?; + // validate the received mixnet contract address + let mixnet_contract_address = deps.api.addr_validate(&msg.mixnet_contract_address)?; + + // ADMIN is set to the address that instantiated the contract + ADMIN.save(deps.storage, &info.sender)?; + MIXNET_CONTRACT_ADDRESS.save(deps.storage, &mixnet_contract_address)?; MIX_DENOM.save(deps.storage, &msg.mix_denom)?; + + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::default()) } #[entry_point] -pub fn migrate(deps: DepsMut<'_>, _env: Env, msg: MigrateMsg) -> Result { - migrate_to_v2_mixnet_contract(deps, msg) +pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { + // this is the first migration that uses cw2 standard and thus the value in the storage doesn't yet exist + // set it instead. + if matches!( + cw2::get_contract_version(deps.storage), + Err(StdError::NotFound { .. }) + ) { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + } + + // note: don't remove this particular bit of code as we have to ALWAYS check whether we have to update the stored version + let version: Version = + CONTRACT_VERSION + .parse() + .map_err(|error: semver::Error| ContractError::SemVerFailure { + value: CONTRACT_VERSION.to_string(), + error_message: error.to_string(), + })?; + + let storage_version_raw = cw2::get_contract_version(deps.storage)?.version; + let storage_version: Version = + storage_version_raw + .parse() + .map_err(|error: semver::Error| ContractError::SemVerFailure { + value: storage_version_raw, + error_message: error.to_string(), + })?; + + if storage_version < version { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + // If state structure changed in any contract version in the way migration is needed, it + // should occur here + } + + Ok(Response::new()) } #[entry_point] @@ -65,13 +123,20 @@ pub fn execute( label, } => try_create_family(info, deps, owner_signature, label), ExecuteMsg::JoinFamily { - signature, + node_identity_signature, + family_signature, family_head, - } => try_join_family(info, deps, signature, family_head), + } => try_join_family( + info, + deps, + node_identity_signature, + family_signature, + family_head, + ), ExecuteMsg::LeaveFamily { - signature, + node_identity_signature, family_head, - } => try_leave_family(info, deps, signature, family_head), + } => try_leave_family(info, deps, node_identity_signature, family_head), ExecuteMsg::KickFamilyMember { signature, member } => { try_kick_family_member(info, deps, signature, member) } @@ -94,12 +159,15 @@ pub fn execute( ExecuteMsg::UpdateMixnetAddress { address } => { try_update_mixnet_address(address, info, deps) } - ExecuteMsg::DelegateToMixnode { mix_id, amount } => { - try_delegate_to_mixnode(mix_id, amount, info, env, deps) - } - ExecuteMsg::UndelegateFromMixnode { mix_id } => { - try_undelegate_from_mixnode(mix_id, info, deps) - } + ExecuteMsg::DelegateToMixnode { + mix_id, + amount, + on_behalf_of, + } => try_delegate_to_mixnode(mix_id, amount, on_behalf_of, info, env, deps), + ExecuteMsg::UndelegateFromMixnode { + mix_id, + on_behalf_of, + } => try_undelegate_from_mixnode(mix_id, on_behalf_of, info, deps), ExecuteMsg::CreateAccount { owner_address, staking_address, @@ -171,20 +239,26 @@ pub fn try_create_family( pub fn try_join_family( info: MessageInfo, deps: DepsMut, - signature: String, + node_identity_signature: String, + family_signature: String, family_head: String, ) -> Result { let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; - account.try_join_family(deps.storage, signature, &family_head) + account.try_join_family( + deps.storage, + node_identity_signature, + family_signature, + &family_head, + ) } pub fn try_leave_family( info: MessageInfo, deps: DepsMut, - signature: String, + node_identity_signature: String, family_head: String, ) -> Result { let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; - account.try_leave_family(deps.storage, signature, &family_head) + account.try_leave_family(deps.storage, node_identity_signature, &family_head) } pub fn try_kick_family_member( info: MessageInfo, @@ -245,7 +319,9 @@ pub fn try_update_mixnet_address( if info.sender != ADMIN.load(deps.storage)? { return Err(ContractError::NotAdmin(info.sender.as_str().to_string())); } - MIXNET_CONTRACT_ADDRESS.save(deps.storage, &address)?; + let mixnet_contract_address = deps.api.addr_validate(&address)?; + + MIXNET_CONTRACT_ADDRESS.save(deps.storage, &mixnet_contract_address)?; Ok(Response::default()) } @@ -313,6 +389,13 @@ fn try_update_staking_address( info: MessageInfo, deps: DepsMut<'_>, ) -> Result { + if let Some(ref to_address) = to_address { + if account_from_address(to_address, deps.storage, deps.api).is_ok() { + // do not allow setting staking address to an existing account's address + return Err(ContractError::StakingAccountExists(to_address.to_string())); + } + } + let address = info.sender.clone(); let to_address = to_address.and_then(|x| deps.api.addr_validate(&x).ok()); let mut account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; @@ -454,13 +537,31 @@ fn try_track_undelegation( fn try_delegate_to_mixnode( mix_id: MixId, amount: Coin, + on_behalf_of: Option, info: MessageInfo, env: Env, deps: DepsMut<'_>, ) -> Result { + // TODO + // as of 01.02.23 + // thus restricting it to 25, which is more than double of that, doesn't seem too unreasonable. + + // while this might not be the best workaround, if user wishes to delegate more tokens towards the same node + // they could remove the existing delegation (thus removing all separate entries from the storage) + // and re-delegate it with the reclaimed amount (which will include all rewards). + let mix_denom = MIX_DENOM.load(deps.storage)?; let amount = validate_funds(&[amount], mix_denom)?; - let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; + + let account = match on_behalf_of { + Some(account_owner) => { + let account = account_from_address(&account_owner, deps.storage, deps.api)?; + ensure_staking_permission(&info.sender, &account)?; + account + } + // you're the owner, you can do what you want + None => account_from_address(info.sender.as_str(), deps.storage, deps.api)?, + }; account.try_delegate_to_mixnode(mix_id, amount, &env, deps.storage) } @@ -488,10 +589,19 @@ fn try_claim_delegator_reward( /// Undelegates from a mixnode, sends [mixnet_contract_common::ExecuteMsg::UndelegateFromMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS]. fn try_undelegate_from_mixnode( mix_id: MixId, + on_behalf_of: Option, info: MessageInfo, deps: DepsMut<'_>, ) -> Result { - let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; + let account = match on_behalf_of { + Some(account_owner) => { + let account = account_from_address(&account_owner, deps.storage, deps.api)?; + ensure_staking_permission(&info.sender, &account)?; + account + } + // you're the owner, you can do what you want + None => account_from_address(info.sender.as_str(), deps.storage, deps.api)?, + }; account.try_undelegate_from_mixnode(mix_id, deps.storage) } @@ -499,7 +609,7 @@ fn try_undelegate_from_mixnode( /// Creates a new periodic vesting account, and deposits funds to vest into the contract. /// /// Callable by ADMIN only, see [instantiate]. -fn try_create_periodic_vesting_account( +pub(crate) fn try_create_periodic_vesting_account( owner_address: &str, staking_address: Option, vesting_spec: Option, @@ -568,6 +678,20 @@ fn try_create_periodic_vesting_account( pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result { let query_res = match msg { QueryMsg::GetContractVersion {} => to_binary(&get_contract_version()), + QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?), + QueryMsg::GetAccountsPaged { + start_next_after, + limit, + } => to_binary(&try_get_all_accounts(deps, start_next_after, limit)?), + QueryMsg::GetAccountsVestingCoinsPaged { + start_next_after, + limit, + } => to_binary(&try_get_all_accounts_vesting_coins( + deps, + env, + start_next_after, + limit, + )?), QueryMsg::LockedCoins { vesting_account_address, block_time, @@ -613,24 +737,38 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result to_binary(&try_get_original_vesting(&vesting_account_address, deps)?), - QueryMsg::GetDelegatedFree { - block_time, + QueryMsg::GetHistoricalVestingStakingReward { vesting_account_address, - } => to_binary(&try_get_delegated_free( - block_time, + } => to_binary(&try_get_historical_vesting_staking_reward( &vesting_account_address, - env, deps, )?), - QueryMsg::GetDelegatedVesting { - block_time, + QueryMsg::GetSpendableVestedCoins { vesting_account_address, - } => to_binary(&try_get_delegated_vesting( - block_time, + } => to_binary(&try_get_spendable_vested_coins( &vesting_account_address, - env, deps, + env, )?), + QueryMsg::GetSpendableRewardCoins { + vesting_account_address, + } => to_binary(&try_get_spendable_reward_coins( + &vesting_account_address, + deps, + env, + )?), + QueryMsg::GetDelegatedCoins { + vesting_account_address, + } => to_binary(&try_get_delegated_coins(&vesting_account_address, deps)?), + QueryMsg::GetPledgedCoins { + vesting_account_address, + } => to_binary(&try_get_pledged_coins(&vesting_account_address, deps)?), + QueryMsg::GetStakedCoins { + vesting_account_address, + } => to_binary(&try_get_staked_coins(&vesting_account_address, deps)?), + QueryMsg::GetWithdrawnCoins { + vesting_account_address, + } => to_binary(&try_get_withdrawn_coins(&vesting_account_address, deps)?), QueryMsg::GetAccount { address } => to_binary(&try_get_account(&address, deps)?), QueryMsg::GetMixnode { address } => to_binary(&try_get_mixnode(&address, deps)?), QueryMsg::GetGateway { address } => to_binary(&try_get_gateway(&address, deps)?), @@ -682,13 +820,83 @@ pub fn get_contract_version() -> ContractBuildInformation { ContractBuildInformation { build_timestamp: env!("VERGEN_BUILD_TIMESTAMP").to_string(), build_version: env!("VERGEN_BUILD_SEMVER").to_string(), - commit_sha: env!("VERGEN_GIT_SHA").to_string(), - commit_timestamp: env!("VERGEN_GIT_COMMIT_TIMESTAMP").to_string(), - commit_branch: env!("VERGEN_GIT_BRANCH").to_string(), + commit_sha: option_env!("VERGEN_GIT_SHA").unwrap_or("NONE").to_string(), + commit_timestamp: option_env!("VERGEN_GIT_COMMIT_TIMESTAMP") + .unwrap_or("NONE") + .to_string(), + commit_branch: option_env!("VERGEN_GIT_BRANCH") + .unwrap_or("NONE") + .to_string(), rustc_version: env!("VERGEN_RUSTC_SEMVER").to_string(), } } +pub fn try_get_all_accounts( + deps: Deps<'_>, + start_after: Option, + limit: Option, +) -> Result { + let limit = limit.unwrap_or(150).min(250) as usize; + + let start = start_after + .map(|raw| deps.api.addr_validate(&raw).map(Bound::exclusive)) + .transpose()?; + + let accounts = ACCOUNTS + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|res| { + res.map(|(_, account)| BaseVestingAccountInfo { + account_id: account.storage_key(), + owner: account.owner_address, + }) + }) + .collect::>>()?; + + let start_next_after = accounts.last().map(|acc| acc.owner.clone()); + + Ok(AccountsResponse { + accounts, + start_next_after, + }) +} + +pub fn try_get_all_accounts_vesting_coins( + deps: Deps<'_>, + env: Env, + start_after: Option, + limit: Option, +) -> Result { + let limit = limit.unwrap_or(150).min(250) as usize; + + let start = start_after + .map(|raw| deps.api.addr_validate(&raw).map(Bound::exclusive)) + .transpose()?; + + let accounts = ACCOUNTS + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|res| { + res.map(|(_, account)| { + account + .get_vesting_coins(None, &env, deps.storage) + .map(|still_vesting| AccountVestingCoins { + account_id: account.storage_key(), + owner: account.owner_address, + still_vesting, + }) + }) + }) + .collect::, _>>>()??; + + let start_next_after = accounts.last().map(|acc| acc.owner.clone()); + + Ok(VestingCoinsResponse { + accounts, + start_next_after, + }) +} + /// Gets currently locked coins, see [crate::traits::VestingAccount::locked_coins] pub fn try_get_locked_coins( vesting_account_address: &str, @@ -760,26 +968,70 @@ pub fn try_get_original_vesting( account.get_original_vesting() } -/// See [crate::traits::VestingAccount::get_delegated_free] -pub fn try_get_delegated_free( - block_time: Option, +pub fn try_get_historical_vesting_staking_reward( vesting_account_address: &str, - env: Env, deps: Deps<'_>, ) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; - account.get_delegated_free(block_time, &env, deps.storage) + account.get_historical_vested_staking_rewards(deps.storage) } -/// See [crate::traits::VestingAccount::get_delegated_vesting] -pub fn try_get_delegated_vesting( - block_time: Option, +pub fn try_get_spendable_vested_coins( vesting_account_address: &str, + deps: Deps<'_>, env: Env, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + account.spendable_vested_coins(None, &env, deps.storage) +} + +pub fn try_get_spendable_reward_coins( + vesting_account_address: &str, + deps: Deps<'_>, + env: Env, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + account.spendable_reward_coins(None, &env, deps.storage) +} + +pub fn try_get_delegated_coins( + vesting_account_address: &str, deps: Deps<'_>, ) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; - account.get_delegated_vesting(block_time, &env, deps.storage) + let denom = MIX_DENOM.load(deps.storage)?; + let amount = account.total_delegations(deps.storage)?; + Ok(Coin { denom, amount }) +} + +pub fn try_get_pledged_coins( + vesting_account_address: &str, + deps: Deps<'_>, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + let denom = MIX_DENOM.load(deps.storage)?; + let amount = account.total_pledged(deps.storage)?; + Ok(Coin { denom, amount }) +} + +pub fn try_get_staked_coins( + vesting_account_address: &str, + deps: Deps<'_>, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + let denom = MIX_DENOM.load(deps.storage)?; + let amount = account.total_staked(deps.storage)?; + Ok(Coin { denom, amount }) +} + +pub fn try_get_withdrawn_coins( + vesting_account_address: &str, + deps: Deps<'_>, +) -> Result { + let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; + let denom = MIX_DENOM.load(deps.storage)?; + let amount = account.load_withdrawn(deps.storage)?; + Ok(Coin { denom, amount }) } /// Returns timestamps at which delegations were made @@ -855,3 +1107,15 @@ fn validate_funds(funds: &[Coin], mix_denom: String) -> Result Result<(), ContractError> { + if let Some(staking_address) = account.staking_address() { + if staking_address == addr { + return Ok(()); + } + } + Err(ContractError::InvalidStakingAccount { + address: addr.clone(), + for_account: account.owner_address(), + }) +} diff --git a/contracts/vesting/src/errors.rs b/contracts/vesting/src/errors.rs index 5244f4330d..195e12e697 100644 --- a/contracts/vesting/src/errors.rs +++ b/contracts/vesting/src/errors.rs @@ -1,4 +1,5 @@ -use cosmwasm_std::{Addr, StdError, Uint128}; +use crate::storage::AccountStorageKey; +use cosmwasm_std::{Addr, OverflowError, StdError, Uint128}; use mixnet_contract_common::MixId; use thiserror::Error; @@ -6,50 +7,99 @@ use thiserror::Error; pub enum ContractError { #[error("VESTING ({}): {0}", line!())] Std(#[from] StdError), + + #[error("VESTING: {0}")] + OverflowError(#[from] OverflowError), + #[error("VESTING ({}): Account does not exist - {0}", line!())] NoAccountForAddress(String), + #[error("VESTING ({}): Only admin can perform this action, {0} is not admin", line!())] NotAdmin(String), + #[error("VESTING ({}): Balance not found for existing account ({0}), this is a bug", line!())] NoBalanceForAddress(String), + #[error("VESTING ({}): Insufficient balance for address {0} -> {1}", line!())] InsufficientBalance(String, u128), + #[error("VESTING ({}): Insufficient spendable balance for address {0} -> {1}", line!())] InsufficientSpendable(String, u128), + #[error( "VESTING ({}):Only delegation owner can perform delegation actions, {0} is not the delegation owner" , line!())] NotDelegate(String), + #[error("VESTING ({}): Total vesting amount is inprobably low -> {0}, this is likely an error", line!())] ImprobableVestingAmount(u128), + #[error("VESTING ({}): Address {0} has already bonded a node", line!())] AlreadyBonded(String), + #[error("VESTING ({}): Received empty funds vector", line!())] EmptyFunds, + #[error("VESTING ({}): Received wrong denom: {0}, expected {1}", line!())] WrongDenom(String, String), + #[error("VESTING ({}): Received multiple denoms, expected 1", line!())] MultipleDenoms, + #[error("VESTING ({}): No delegations found for account {0}, mix_identity {1}", line!())] NoSuchDelegation(Addr, MixId), + #[error("VESTING ({}): Only mixnet contract can perform this operation, got {0}", line!())] NotMixnetContract(Addr), + #[error("VESTING ({}): Calculation underflowed", line!())] Underflow, + #[error("VESTING ({}): No bond found for account {0}", line!())] NoBondFound(String), + #[error("VESTING ({}): Action can only be executed by account owner -> {0}", line!())] NotOwner(String), + #[error("VESTING ({}): Invalid address: {0}", line!())] InvalidAddress(String), + #[error("VESTING ({}): Account already exists: {0}", line!())] AccountAlreadyExists(String), + #[error("VESTING ({}): Staking account already exists: {0}", line!())] StakingAccountAlreadyExists(String), + #[error("VESTING ({}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", line!())] MinVestingFunds { sent: u128, need: u128 }, + #[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())] LockedPledgeCapReached { current: Uint128, cap: Uint128 }, + #[error("VESTING: ({}: Account owned by {owner} has unpopulated vesting periods!", line!())] UnpopulatedVestingPeriods { owner: Addr }, + + #[error("VESTING: Vesting account associated with {0} already exists, only addresses with not existing vesting accounts can be added as staking addresses")] + StakingAccountExists(String), + + #[error("VESTING: {address} is not permitted to perform staking on behalf of {for_account}")] + InvalidStakingAccount { address: Addr, for_account: Addr }, + + #[error("VESTING: {address} ({acc_id} has already performed {num} individual delegations towards {mix_id}. No further delegations are allowed. Please consider consolidating those delegations instead. The current cap is {cap}.")] + TooManyDelegations { + address: Addr, + acc_id: AccountStorageKey, + mix_id: MixId, + num: u32, + cap: u32, + }, + + #[error("VESTING: Failed to parse {value} into a valid SemVer version: {error_message}")] + SemVerFailure { + value: String, + error_message: String, + }, + + #[error("VESTING: {message}")] + Other { message: String }, } diff --git a/contracts/vesting/src/queued_migrations.rs b/contracts/vesting/src/queued_migrations.rs index 9bb44331a1..87d5c392c8 100644 --- a/contracts/vesting/src/queued_migrations.rs +++ b/contracts/vesting/src/queued_migrations.rs @@ -1,15 +1,2 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 - -use crate::errors::ContractError; -use crate::storage::MIXNET_CONTRACT_ADDRESS; -use cosmwasm_std::{DepsMut, Response}; -use vesting_contract_common::MigrateMsg; - -pub fn migrate_to_v2_mixnet_contract( - deps: DepsMut<'_>, - msg: MigrateMsg, -) -> Result { - MIXNET_CONTRACT_ADDRESS.save(deps.storage, &msg.v2_mixnet_contract_address)?; - Ok(Response::new()) -} diff --git a/contracts/vesting/src/storage.rs b/contracts/vesting/src/storage.rs index a1216fa87e..d1c5af31e3 100644 --- a/contracts/vesting/src/storage.rs +++ b/contracts/vesting/src/storage.rs @@ -1,56 +1,114 @@ use crate::errors::ContractError; use crate::vesting::Account; +use cosmwasm_std::Order; use cosmwasm_std::{Addr, Api, Storage, Uint128}; use cw_storage_plus::{Item, Map}; use mixnet_contract_common::{IdentityKey, MixId}; use vesting_contract_common::PledgeData; pub(crate) type BlockTimestampSecs = u64; +pub(crate) type AccountStorageKey = u32; -pub const KEY: Item<'_, u32> = Item::new("key"); -const ACCOUNTS: Map<'_, String, Account> = Map::new("acc"); -// Holds data related to individual accounts -const BALANCES: Map<'_, u32, Uint128> = Map::new("blc"); -const WITHDRAWNS: Map<'_, u32, Uint128> = Map::new("wthd"); -const BOND_PLEDGES: Map<'_, u32, PledgeData> = Map::new("bnd"); -const GATEWAY_PLEDGES: Map<'_, u32, PledgeData> = Map::new("gtw"); -pub const _OLD_DELEGATIONS: Map<'_, (u32, IdentityKey, BlockTimestampSecs), Uint128> = +/// Counter for the unique, monotonically increasing storage key id for the vesting account data. +pub const KEY: Item<'_, AccountStorageKey> = Item::new("key"); + +/// Storage map containing vesting account information associated with particular owner address. +pub const ACCOUNTS: Map<'_, Addr, Account> = Map::new("acc"); + +/// Storage map containing information about amount of tokens associated with particular vesting account +/// that are currently present in the contract (and have not been withdrawn or staked in the mixnet contract) +// note: this assumes I understood the intent behind this correctly +const BALANCES: Map<'_, AccountStorageKey, Uint128> = Map::new("blc"); + +/// Storage map containing information about amount of tokens withdrawn from the contract by a particular vesting account. +const WITHDRAWNS: Map<'_, AccountStorageKey, Uint128> = Map::new("wthd"); + +/// Storage map containing information about amount of tokens pledged towards bonding mixnodes +/// in the mixnet contract using a particular vesting account. +const BOND_PLEDGES: Map<'_, AccountStorageKey, PledgeData> = Map::new("bnd"); + +/// Storage map containing information about amount of tokens pledged towards bonding gateways +/// in the mixnet contract using a particular vesting account. +const GATEWAY_PLEDGES: Map<'_, AccountStorageKey, PledgeData> = Map::new("gtw"); + +/// Old, pre-v2 migration, storage map that used to contain information about tokens delegated +/// towards particular mixnodes in the mixnet contract with given vesting account. +/// It should be completely empty. +pub const _OLD_DELEGATIONS: Map<'_, (AccountStorageKey, IdentityKey, BlockTimestampSecs), Uint128> = Map::new("dlg"); -pub const DELEGATIONS: Map<'_, (u32, MixId, BlockTimestampSecs), Uint128> = Map::new("dlg_v2"); -pub const ADMIN: Item<'_, String> = Item::new("adm"); -pub const MIXNET_CONTRACT_ADDRESS: Item<'_, String> = Item::new("mix"); + +/// Storage map containing information about tokens delegated towards particular mixnodes +/// in the mixnet contract with given vesting account. +pub const DELEGATIONS: Map<'_, (AccountStorageKey, MixId, BlockTimestampSecs), Uint128> = + Map::new("dlg_v2"); + +/// Explicit contract admin that is allowed, among other things, to create new vesting accounts. +pub const ADMIN: Item<'_, Addr> = Item::new("adm"); + +/// Address of the mixnet contract. +pub const MIXNET_CONTRACT_ADDRESS: Item<'_, Addr> = Item::new("mix"); + +/// The denomination of coin used for staking. pub const MIX_DENOM: Item<'_, String> = Item::new("den"); pub fn save_delegation( - key: (u32, MixId, BlockTimestampSecs), + key: (AccountStorageKey, MixId, BlockTimestampSecs), amount: Uint128, storage: &mut dyn Storage, ) -> Result<(), ContractError> { - DELEGATIONS.save(storage, key, &amount)?; + let existing_delegation_amount = if let Some(delegation) = DELEGATIONS.may_load(storage, key)? { + delegation + } else { + Uint128::zero() + }; + let new_delegations_amount = existing_delegation_amount + amount; + DELEGATIONS.save(storage, key, &new_delegations_amount)?; Ok(()) } pub fn remove_delegation( - key: (u32, MixId, BlockTimestampSecs), + key: (AccountStorageKey, MixId, BlockTimestampSecs), storage: &mut dyn Storage, ) -> Result<(), ContractError> { DELEGATIONS.remove(storage, key); Ok(()) } -pub fn delete_account(address: &Addr, storage: &mut dyn Storage) -> Result<(), ContractError> { - ACCOUNTS.remove(storage, address.to_owned().to_string()); - Ok(()) +pub fn load_delegation_timestamps( + prefix: (AccountStorageKey, MixId), + storage: &dyn Storage, +) -> Result, ContractError> { + let block_timestamps = DELEGATIONS + .prefix(prefix) + .keys(storage, None, None, Order::Ascending) + .collect::, _>>()?; + Ok(block_timestamps) } -pub fn load_withdrawn(key: u32, storage: &dyn Storage) -> Result { +pub fn count_subdelegations_for_mix( + prefix: (AccountStorageKey, MixId), + storage: &dyn Storage, +) -> u32 { + DELEGATIONS + .prefix(prefix) + .keys(storage, None, None, Order::Ascending) + .count() as u32 +} + +pub fn load_withdrawn( + key: AccountStorageKey, + storage: &dyn Storage, +) -> Result { Ok(WITHDRAWNS .may_load(storage, key) .unwrap_or(None) .unwrap_or_else(Uint128::zero)) } -pub fn load_balance(key: u32, storage: &dyn Storage) -> Result { +pub fn load_balance( + key: AccountStorageKey, + storage: &dyn Storage, +) -> Result { Ok(BALANCES .may_load(storage, key) .unwrap_or(None) @@ -58,7 +116,7 @@ pub fn load_balance(key: u32, storage: &dyn Storage) -> Result Result<(), ContractError> { @@ -67,7 +125,7 @@ pub fn save_balance( } pub fn save_withdrawn( - key: u32, + key: AccountStorageKey, value: Uint128, storage: &mut dyn Storage, ) -> Result<(), ContractError> { @@ -76,19 +134,22 @@ pub fn save_withdrawn( } pub fn load_bond_pledge( - key: u32, + key: AccountStorageKey, storage: &dyn Storage, ) -> Result, ContractError> { Ok(BOND_PLEDGES.may_load(storage, key).unwrap_or(None)) } -pub fn remove_bond_pledge(key: u32, storage: &mut dyn Storage) -> Result<(), ContractError> { +pub fn remove_bond_pledge( + key: AccountStorageKey, + storage: &mut dyn Storage, +) -> Result<(), ContractError> { BOND_PLEDGES.remove(storage, key); Ok(()) } pub fn save_bond_pledge( - key: u32, + key: AccountStorageKey, value: &PledgeData, storage: &mut dyn Storage, ) -> Result<(), ContractError> { @@ -97,14 +158,14 @@ pub fn save_bond_pledge( } pub fn load_gateway_pledge( - key: u32, + key: AccountStorageKey, storage: &dyn Storage, ) -> Result, ContractError> { Ok(GATEWAY_PLEDGES.may_load(storage, key).unwrap_or(None)) } pub fn save_gateway_pledge( - key: u32, + key: AccountStorageKey, value: &PledgeData, storage: &mut dyn Storage, ) -> Result<(), ContractError> { @@ -112,32 +173,34 @@ pub fn save_gateway_pledge( Ok(()) } -pub fn remove_gateway_pledge(key: u32, storage: &mut dyn Storage) -> Result<(), ContractError> { +pub fn remove_gateway_pledge( + key: AccountStorageKey, + storage: &mut dyn Storage, +) -> Result<(), ContractError> { GATEWAY_PLEDGES.remove(storage, key); Ok(()) } pub fn save_account(account: &Account, storage: &mut dyn Storage) -> Result<(), ContractError> { - // This is a bit dirty, but its a simple way to allow for both staking account and owner to load it from storage - if let Some(staking_address) = account.staking_address() { - ACCOUNTS.save(storage, staking_address.to_owned().to_string(), account)?; - } - ACCOUNTS.save(storage, account.owner_address().to_string(), account)?; + ACCOUNTS.save(storage, account.owner_address(), account)?; Ok(()) } pub fn load_account( - address: &Addr, + address: Addr, storage: &dyn Storage, ) -> Result, ContractError> { - Ok(ACCOUNTS - .may_load(storage, address.to_owned().to_string()) - .unwrap_or(None)) + Ok(ACCOUNTS.may_load(storage, address).unwrap_or(None)) } -fn validate_account(address: &Addr, storage: &dyn Storage) -> Result { - load_account(address, storage)? - .ok_or_else(|| ContractError::NoAccountForAddress(address.as_str().to_string())) +pub fn delete_account(address: Addr, storage: &mut dyn Storage) -> Result<(), ContractError> { + ACCOUNTS.remove(storage, address); + Ok(()) +} + +fn validate_account(address: Addr, storage: &dyn Storage) -> Result { + load_account(address.clone(), storage)? + .ok_or_else(|| ContractError::NoAccountForAddress(address.into_string())) } pub fn account_from_address( @@ -145,5 +208,5 @@ pub fn account_from_address( storage: &dyn Storage, api: &dyn Api, ) -> Result { - validate_account(&api.addr_validate(address)?, storage) + validate_account(api.addr_validate(address)?, storage) } diff --git a/contracts/vesting/src/support/tests.rs b/contracts/vesting/src/support/tests.rs index 8f24359f52..831b071663 100644 --- a/contracts/vesting/src/support/tests.rs +++ b/contracts/vesting/src/support/tests.rs @@ -1,14 +1,320 @@ #[cfg(test)] pub mod helpers { - use crate::contract::instantiate; + + // TODO: once https://github.com/nymtech/nym/pull/3040 gets merged, + // the `ContractState` should replace the below + #[allow(unused)] + mod state_dump_decoder { + use base64::{engine::general_purpose, Engine}; + use serde::{Deserialize, Serialize}; + use std::fs::File; + use std::path::Path; + + #[derive(Serialize, Deserialize, Debug)] + pub struct RawState { + pub height: String, + pub result: Vec, + } + + impl RawState { + pub fn decode(self) -> DecodedState { + DecodedState { + height: self.height.parse().unwrap(), + result: self + .result + .into_iter() + .map(|raw| DecodedKV { + key: hex::decode(&raw.key).unwrap(), + value: general_purpose::STANDARD.decode(&raw.value).unwrap(), + }) + .collect(), + } + } + + pub fn from_file>(path: P) -> Self { + let file = File::open(path).expect("failed to open specified file"); + serde_json::from_reader(file).expect("failed to parse specified file") + } + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct RawKV { + // hex + pub key: String, + + // base64 + pub value: String, + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct DecodedKV { + pub key: Vec, + pub value: Vec, + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct DecodedState { + pub height: u64, + pub result: Vec, + } + + impl DecodedState { + pub fn find_value(&self, key: &[u8]) -> Option> { + self.result + .iter() + .find(|kv| kv.key == key) + .map(|kv| kv.value.clone()) + } + } + } + + use crate::contract::{instantiate, try_create_periodic_vesting_account}; + use crate::storage::{ACCOUNTS, ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM}; + use crate::support::tests::helpers::state_dump_decoder::RawState; + use crate::traits::VestingAccount; use crate::vesting::{populate_vesting_periods, Account}; use contracts_common::Percent; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; - use cosmwasm_std::{Addr, Coin, Empty, Env, MemoryStorage, OwnedDeps, Storage, Uint128}; + use cosmwasm_std::{ + coin, Addr, BlockInfo, Coin, ContractInfo, Deps, DepsMut, Empty, Env, MemoryStorage, + MessageInfo, OwnedDeps, Storage, Timestamp, Uint128, + }; + use rand_chacha::rand_core::SeedableRng; + use rand_chacha::ChaCha20Rng; + use std::path::Path; use std::str::FromStr; use vesting_contract_common::messages::{InitMsg, VestingSpecification}; use vesting_contract_common::PledgeCap; + // use rng with constant seed for all tests so that they would be deterministic + #[allow(unused)] + pub fn test_rng() -> ChaCha20Rng { + let dummy_seed = [42u8; 32]; + rand_chacha::ChaCha20Rng::from_seed(dummy_seed) + } + + #[allow(unused)] + pub struct TestSetup { + pub deps: OwnedDeps>, + pub env: Env, + pub rng: ChaCha20Rng, + + pub admin: MessageInfo, + } + + #[allow(unused)] + impl TestSetup { + pub fn new() -> Self { + let deps = init_contract(); + let admin = ADMIN.load(deps.as_ref().storage).unwrap(); + + TestSetup { + deps, + env: mock_env(), + rng: test_rng(), + admin: mock_info(admin.as_str(), &[]), + } + } + + pub fn new_from_state_dump>(dump_file: P) -> Self { + let state = RawState::from_file(dump_file).decode(); + + let mut deps = mock_dependencies(); + for kv in state.result { + deps.storage.set(&kv.key, &kv.value) + } + + let admin = ADMIN.load(deps.as_ref().storage).unwrap(); + let env = Env { + block: BlockInfo { + height: 5633424, + time: Timestamp::from_seconds(1676025955), + chain_id: "nyx".to_string(), + }, + transaction: None, + contract: ContractInfo { + address: Addr::unchecked( + "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw", + ), + }, + }; + + TestSetup { + deps, + env, + rng: test_rng(), + admin: mock_info(admin.as_str(), &[]), + } + } + + pub fn create_vesting_account( + &mut self, + owner: &str, + spec: Option, + amount: u128, + ) -> Account { + let mut sender = self.admin(); + sender.funds = vec![self.coin(amount)]; + let env = self.env(); + try_create_periodic_vesting_account( + owner, + None, + spec, + Some(PledgeCap::Percent( + Percent::from_percentage_value(100).unwrap(), + )), + sender, + env, + self.deps_mut(), + ) + .unwrap(); + + self.unchecked_account(Addr::unchecked(owner)) + } + + pub fn deps(&self) -> Deps<'_> { + self.deps.as_ref() + } + + pub fn deps_mut(&mut self) -> DepsMut<'_> { + self.deps.as_mut() + } + + pub fn env(&self) -> Env { + self.env.clone() + } + + pub fn admin(&self) -> MessageInfo { + self.admin.clone() + } + + pub fn mixnet_contract(&self) -> Addr { + MIXNET_CONTRACT_ADDRESS.load(self.deps().storage).unwrap() + } + + pub fn coin(&self, amount: u128) -> Coin { + let denom = MIX_DENOM.load(self.deps().storage).unwrap(); + coin(amount, denom) + } + + pub fn unchecked_account(&self, address: Addr) -> Account { + ACCOUNTS.load(self.deps().storage, address).unwrap() + } + + pub fn print_account_coins(&self, account: &Account) { + let original = self.original_vesting(account); + let balance = self.balance(account); + let withdrawn = self.withdrawn(account); + let vesting = self.vesting_coins(account); + let vested = self.vested_coins(account); + + let locked = self.locked_coins(account); + let spendable = self.spendable_coins(account); + let spendable_vested = self.spendable_vested_coins(account); + let spendable_reward = self.spendable_reward_coins(account); + + assert_eq!( + spendable.amount, + spendable_vested.amount + spendable_reward.amount + ); + + let total_delegated = self.delegated_total(account); + let historical_rewards = self.historical_staking_rewards(account); + + let pretty = format!( + r#" +{:<20}{original} +{:<20}{vesting} +{:<20}{vested} +{:<20}{balance} +{:<20}{withdrawn} +{:<20}{historical_rewards} +{:<20}{locked} +{:<20}{spendable} +{:<20}{spendable_vested} +{:<20}{spendable_reward} +{:<20}{total_delegated} +"#, + "original", + "vesting", + "vested:", + "balance", + "withdrawn", + "historical rewards", + "locked", + "spendable", + "spendable vested", + "spendable reward", + "total delegated", + ); + + println!("{pretty}") + } + + pub fn original_vesting(&self, account: &Account) -> Coin { + account.get_original_vesting().unwrap().amount() + } + + pub fn balance(&self, account: &Account) -> Coin { + self.coin(account.load_balance(self.deps().storage).unwrap().u128()) + } + + pub fn withdrawn(&self, account: &Account) -> Coin { + self.coin(account.load_withdrawn(self.deps().storage).unwrap().u128()) + } + + pub fn vesting_coins(&self, account: &Account) -> Coin { + account + .get_vesting_coins(None, &self.env, self.deps().storage) + .unwrap() + } + + pub fn vested_coins(&self, account: &Account) -> Coin { + account + .get_vested_coins(None, &self.env, self.deps().storage) + .unwrap() + } + + pub fn locked_coins(&self, account: &Account) -> Coin { + account + .locked_coins(None, &self.env, self.deps().storage) + .unwrap() + } + + pub fn spendable_coins(&self, account: &Account) -> Coin { + account + .spendable_coins(None, &self.env, self.deps().storage) + .unwrap() + } + + pub fn spendable_vested_coins(&self, account: &Account) -> Coin { + account + .spendable_vested_coins(None, &self.env, self.deps().storage) + .unwrap() + } + + pub fn spendable_reward_coins(&self, account: &Account) -> Coin { + account + .spendable_reward_coins(None, &self.env, self.deps().storage) + .unwrap() + } + + pub fn delegated_total(&self, account: &Account) -> Coin { + self.coin( + account + .total_delegations(self.deps().storage) + .unwrap() + .u128(), + ) + } + + pub fn historical_staking_rewards(&self, account: &Account) -> Coin { + account + .get_historical_vested_staking_rewards(self.deps().storage) + .unwrap() + } + } + pub const TEST_COIN_DENOM: &str = "unym"; pub fn init_contract() -> OwnedDeps> { @@ -20,7 +326,7 @@ pub mod helpers { let env = mock_env(); let info = mock_info("admin", &[]); instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); - return deps; + deps } pub fn vesting_account_mid_fixture(storage: &mut dyn Storage, env: &Env) -> Account { diff --git a/contracts/vesting/src/traits/node_families.rs b/contracts/vesting/src/traits/node_families.rs index 274ac41f94..3e2e6e9863 100644 --- a/contracts/vesting/src/traits/node_families.rs +++ b/contracts/vesting/src/traits/node_families.rs @@ -13,7 +13,8 @@ pub trait NodeFamilies { fn try_join_family( &self, storage: &dyn Storage, - signature: String, + node_identity_signature: String, + family_signature: String, family_head: IdentityKeyRef, ) -> Result; diff --git a/contracts/vesting/src/traits/vesting_account.rs b/contracts/vesting/src/traits/vesting_account.rs index 9d44780fbe..258a750492 100644 --- a/contracts/vesting/src/traits/vesting_account.rs +++ b/contracts/vesting/src/traits/vesting_account.rs @@ -3,14 +3,10 @@ use cosmwasm_std::{Addr, Coin, Env, Storage, Timestamp, Uint128}; use vesting_contract_common::OriginalVestingResponse; pub trait VestingAccount { - fn total_pledged_locked( - &self, - storage: &dyn Storage, - env: &Env, - ) -> Result; + fn total_staked(&self, storage: &dyn Storage) -> Result; /// Returns the set of coins that are not spendable (can still be delegated tough) (i.e. locked), - /// defined as the vesting coins that are not delegated or pledged. + /// defined as vesting coins that are not delegated or pledged. /// /// To get spendable coins of a vesting account, first the total balance must /// be retrieved and the locked tokens can be subtracted from the total balance. @@ -32,6 +28,20 @@ pub trait VestingAccount { storage: &dyn Storage, ) -> Result; + fn spendable_vested_coins( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result; + + fn spendable_reward_coins( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result; + /// See [/vesting-contract/struct.Account.html/method.get_vested_coins] for impl fn get_vested_coins( &self, @@ -57,36 +67,6 @@ pub trait VestingAccount { /// See [/vesting-contract/struct.Account.html/method.get_original_vesting] for impl fn get_original_vesting(&self) -> Result; - /// See [/vesting-contract/struct.Account.html/method.get_delegated_free] for impl - fn get_delegated_free( - &self, - block_time: Option, - env: &Env, - storage: &dyn Storage, - ) -> Result; - - /// See [/vesting-contract/struct.Account.html/method.get_delegated_vesting] for impl - fn get_delegated_vesting( - &self, - block_time: Option, - env: &Env, - storage: &dyn Storage, - ) -> Result; - - /// See [/vesting-contract/struct.Account.html/method.get_pledged_free] for impl - fn get_pledged_free( - &self, - block_time: Option, - env: &Env, - storage: &dyn Storage, - ) -> Result; - /// See [/vesting-contract/struct.Account.html/method.get_pledged_vesting] for impl - fn get_pledged_vesting( - &self, - block_time: Option, - env: &Env, - storage: &dyn Storage, - ) -> Result; /// See [/vesting-contract/struct.Account.html/method.transfer_ownership] for impl fn transfer_ownership( &mut self, @@ -100,4 +80,8 @@ pub trait VestingAccount { storage: &mut dyn Storage, ) -> Result<(), ContractError>; fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>; + fn get_historical_vested_staking_rewards( + &self, + storage: &dyn Storage, + ) -> Result; } diff --git a/contracts/vesting/src/vesting/account/delegating_account.rs b/contracts/vesting/src/vesting/account/delegating_account.rs index 62fa42b21e..beaedf2be2 100644 --- a/contracts/vesting/src/vesting/account/delegating_account.rs +++ b/contracts/vesting/src/vesting/account/delegating_account.rs @@ -1,8 +1,8 @@ +use crate::contract::MAX_PER_MIX_DELEGATIONS; use crate::errors::ContractError; use crate::storage::save_delegation; use crate::storage::MIXNET_CONTRACT_ADDRESS; use crate::traits::DelegatingAccount; -use crate::traits::VestingAccount; use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; use mixnet_contract_common::ExecuteMsg as MixnetExecuteMsg; use mixnet_contract_common::MixId; @@ -36,25 +36,19 @@ impl DelegatingAccount for Account { env: &Env, storage: &mut dyn Storage, ) -> Result { - let current_balance = self.load_balance(storage)?; - let total_pledged_locked = self.total_pledged_locked(storage, env)?; - let total_pledged_after = total_pledged_locked + coin.amount; - let locked_pledge_cap = self.absolute_pledge_cap()?; + let current_balance = self.ensure_valid_additional_stake(&coin, storage)?; + let num_subdelegations = self.num_subdelegations_for_mix(mix_id, storage); - if locked_pledge_cap < total_pledged_after { - return Err(ContractError::LockedPledgeCapReached { - current: total_pledged_after, - cap: locked_pledge_cap, + if num_subdelegations >= MAX_PER_MIX_DELEGATIONS { + return Err(ContractError::TooManyDelegations { + address: self.owner_address.clone(), + acc_id: self.storage_key(), + mix_id, + num: num_subdelegations, + cap: MAX_PER_MIX_DELEGATIONS, }); } - if current_balance < coin.amount { - return Err(ContractError::InsufficientBalance( - self.owner_address().as_str().to_string(), - current_balance.u128(), - )); - } - let msg = MixnetExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, delegate: self.owner_address().into_string(), diff --git a/contracts/vesting/src/vesting/account/gateway_bonding_account.rs b/contracts/vesting/src/vesting/account/gateway_bonding_account.rs index 91cdd41c68..6669593871 100644 --- a/contracts/vesting/src/vesting/account/gateway_bonding_account.rs +++ b/contracts/vesting/src/vesting/account/gateway_bonding_account.rs @@ -2,7 +2,6 @@ use super::PledgeData; use crate::errors::ContractError; use crate::storage::MIXNET_CONTRACT_ADDRESS; use crate::traits::GatewayBondingAccount; -use crate::traits::VestingAccount; use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, Gateway}; use vesting_contract_common::events::{ @@ -20,24 +19,7 @@ impl GatewayBondingAccount for Account { env: &Env, storage: &mut dyn Storage, ) -> Result { - let current_balance = self.load_balance(storage)?; - let total_pledged_locked = self.total_pledged_locked(storage, env)?; - let total_pledged_after = total_pledged_locked + pledge.amount; - let locked_pledge_cap = self.absolute_pledge_cap()?; - - if locked_pledge_cap < total_pledged_after { - return Err(ContractError::LockedPledgeCapReached { - current: total_pledged_after, - cap: locked_pledge_cap, - }); - } - - if current_balance < pledge.amount { - return Err(ContractError::InsufficientBalance( - self.owner_address().as_str().to_string(), - current_balance.u128(), - )); - } + let current_balance = self.ensure_valid_additional_stake(&pledge, storage)?; let pledge_data = if self.load_gateway_pledge(storage)?.is_some() { return Err(ContractError::AlreadyBonded( diff --git a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs index 74917a16c7..93bfb8840c 100644 --- a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs +++ b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs @@ -5,7 +5,6 @@ use super::Account; use crate::errors::ContractError; use crate::storage::MIXNET_CONTRACT_ADDRESS; use crate::traits::MixnodeBondingAccount; -use crate::traits::VestingAccount; use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; use mixnet_contract_common::mixnode::MixNodeConfigUpdate; use mixnet_contract_common::mixnode::MixNodeCostParams; @@ -38,24 +37,7 @@ impl MixnodeBondingAccount for Account { env: &Env, storage: &mut dyn Storage, ) -> Result { - let current_balance = self.load_balance(storage)?; - let total_pledged_locked = self.total_pledged_locked(storage, env)?; - let total_pledged_after = total_pledged_locked + pledge.amount; - let locked_pledge_cap = self.absolute_pledge_cap()?; - - if locked_pledge_cap < total_pledged_after { - return Err(ContractError::LockedPledgeCapReached { - current: total_pledged_after, - cap: locked_pledge_cap, - }); - } - - if current_balance < pledge.amount { - return Err(ContractError::InsufficientBalance( - self.owner_address().as_str().to_string(), - current_balance.u128(), - )); - } + let current_balance = self.ensure_valid_additional_stake(&pledge, storage)?; let pledge_data = if self.load_mixnode_pledge(storage)?.is_some() { return Err(ContractError::AlreadyBonded( @@ -91,24 +73,7 @@ impl MixnodeBondingAccount for Account { env: &Env, storage: &mut dyn Storage, ) -> Result { - let current_balance = self.load_balance(storage)?; - let total_pledged_after = - self.total_pledged_locked(storage, env)? + additional_pledge.amount; - let locked_pledge_cap = self.absolute_pledge_cap()?; - - if locked_pledge_cap < total_pledged_after { - return Err(ContractError::LockedPledgeCapReached { - current: total_pledged_after, - cap: locked_pledge_cap, - }); - } - - if current_balance < additional_pledge.amount { - return Err(ContractError::InsufficientBalance( - self.owner_address().as_str().to_string(), - current_balance.u128(), - )); - } + let current_balance = self.ensure_valid_additional_stake(&additional_pledge, storage)?; let mut pledge_data = if let Some(pledge_data) = self.load_mixnode_pledge(storage)? { pledge_data diff --git a/contracts/vesting/src/vesting/account/mod.rs b/contracts/vesting/src/vesting/account/mod.rs index a368431689..01151ddc6c 100644 --- a/contracts/vesting/src/vesting/account/mod.rs +++ b/contracts/vesting/src/vesting/account/mod.rs @@ -1,13 +1,13 @@ use super::VestingPeriod; use crate::errors::ContractError; use crate::storage::{ - load_balance, load_bond_pledge, load_gateway_pledge, load_withdrawn, remove_bond_pledge, - remove_delegation, remove_gateway_pledge, save_account, save_balance, save_bond_pledge, - save_gateway_pledge, save_withdrawn, BlockTimestampSecs, DELEGATIONS, KEY, + count_subdelegations_for_mix, load_balance, load_bond_pledge, load_delegation_timestamps, + load_gateway_pledge, load_withdrawn, remove_bond_pledge, remove_delegation, + remove_gateway_pledge, save_account, save_balance, save_bond_pledge, save_gateway_pledge, + save_withdrawn, AccountStorageKey, BlockTimestampSecs, DELEGATIONS, KEY, }; use crate::traits::VestingAccount; use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128}; -use cw_storage_plus::Bound; use mixnet_contract_common::MixId; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -19,7 +19,7 @@ mod mixnode_bonding_account; mod node_families; mod vesting_account; -fn generate_storage_key(storage: &mut dyn Storage) -> Result { +fn generate_storage_key(storage: &mut dyn Storage) -> Result { let key = KEY.may_load(storage)?.unwrap_or(0) + 1; KEY.save(storage, &key)?; Ok(key) @@ -32,7 +32,7 @@ pub struct Account { pub start_time: Timestamp, pub periods: Vec, pub coin: Coin, - storage_key: u32, + storage_key: AccountStorageKey, #[serde(default)] pub pledge_cap: Option, } @@ -63,6 +63,36 @@ impl Account { Ok(account) } + /// Checks whether the additional stake would be within the cap associated with the account + /// and whether the account has enough tokens for staking. + /// Returns the value of the current balance of the account. + pub fn ensure_valid_additional_stake( + &self, + additional_stake: &Coin, + storage: &dyn Storage, + ) -> Result { + let current_balance = self.load_balance(storage)?; + let current_total_staked = self.total_staked(storage)?; + let total_staked_after = current_total_staked + additional_stake.amount; + let locked_pledge_cap = self.absolute_pledge_cap()?; + + if locked_pledge_cap < total_staked_after { + return Err(ContractError::LockedPledgeCapReached { + current: total_staked_after, + cap: locked_pledge_cap, + }); + } + + if current_balance < additional_stake.amount { + return Err(ContractError::InsufficientBalance( + self.owner_address().as_str().to_string(), + current_balance.u128(), + )); + } + + Ok(current_balance) + } + pub fn pledge_cap(&self) -> PledgeCap { self.pledge_cap.clone().unwrap_or_default() } @@ -244,39 +274,21 @@ impl Account { .is_some() } + pub fn num_subdelegations_for_mix(&self, mix_id: MixId, storage: &dyn Storage) -> u32 { + count_subdelegations_for_mix((self.storage_key(), mix_id), storage) + } + pub fn remove_delegations_for_mix( &self, mix_id: MixId, storage: &mut dyn Storage, ) -> Result<(), ContractError> { - let limit = 50; - let mut start_after = None; - let mut block_heights = Vec::new(); - let mut prev_len = 0; - // TODO: Test this - loop { - block_heights.extend( - DELEGATIONS - .prefix((self.storage_key(), mix_id)) - .keys(storage, start_after, None, Order::Ascending) - .take(limit) - .filter_map(|key| key.ok()), - ); + // note that the limit is implicitly set to `MAX_PER_MIX_DELEGATIONS` + // as it should be impossible to create more delegations than that. + let block_timestamps = load_delegation_timestamps((self.storage_key(), mix_id), storage)?; - if prev_len == block_heights.len() { - break; - } - - prev_len = block_heights.len(); - - start_after = block_heights.last().map(|last| Bound::exclusive(*last)); - if start_after.is_none() { - break; - } - } - - for block_height in block_heights { - remove_delegation((self.storage_key(), mix_id, block_height), storage)?; + for block_timestamp in block_timestamps { + remove_delegation((self.storage_key(), mix_id, block_timestamp), storage)?; } Ok(()) } @@ -293,6 +305,9 @@ impl Account { .fold(Uint128::zero(), |acc, (_key, val)| acc + val)) } + // TODO: this should get reworked... somehow... (maybe with a memoized value?) + // as it's an unbounded iteration that could fail if an account has made a lot of delegations + // (I guess in order of thousands) pub fn total_delegations(&self, storage: &dyn Storage) -> Result { Ok(DELEGATIONS .sub_prefix(self.storage_key()) @@ -301,6 +316,18 @@ impl Account { .fold(Uint128::zero(), |acc, (_key, val)| acc + val)) } + pub fn total_pledged(&self, storage: &dyn Storage) -> Result { + let amount = if let Some(bond) = self + .load_mixnode_pledge(storage)? + .or(self.load_gateway_pledge(storage)?) + { + bond.amount().amount + } else { + Uint128::zero() + }; + Ok(amount) + } + pub fn total_delegations_at_timestamp( &self, storage: &dyn Storage, diff --git a/contracts/vesting/src/vesting/account/node_families.rs b/contracts/vesting/src/vesting/account/node_families.rs index a83efdabf9..edf07d27fd 100644 --- a/contracts/vesting/src/vesting/account/node_families.rs +++ b/contracts/vesting/src/vesting/account/node_families.rs @@ -24,12 +24,14 @@ impl NodeFamilies for Account { fn try_join_family( &self, storage: &dyn Storage, - signature: String, + node_identity_signature: String, + family_signature: String, family_head: IdentityKeyRef, ) -> Result { let msg = MixnetExecuteMsg::JoinFamilyOnBehalf { member_address: self.owner_address().to_string(), - signature, + node_identity_signature, + family_signature, family_head: family_head.to_string(), }; @@ -41,12 +43,12 @@ impl NodeFamilies for Account { fn try_leave_family( &self, storage: &dyn Storage, - signature: String, + node_identity_signature: String, family_head: IdentityKeyRef, ) -> Result { let msg = MixnetExecuteMsg::LeaveFamilyOnBehalf { member_address: self.owner_address().to_string(), - signature, + node_identity_signature, family_head: family_head.to_string(), }; diff --git a/contracts/vesting/src/vesting/account/vesting_account.rs b/contracts/vesting/src/vesting/account/vesting_account.rs index 97c02c0214..5b64b9896e 100644 --- a/contracts/vesting/src/vesting/account/vesting_account.rs +++ b/contracts/vesting/src/vesting/account/vesting_account.rs @@ -2,44 +2,32 @@ use crate::errors::ContractError; use crate::storage::{delete_account, save_account, MIX_DENOM}; use crate::traits::VestingAccount; use cosmwasm_std::{Addr, Coin, Env, Storage, Timestamp, Uint128}; +use std::cmp::min; use vesting_contract_common::{OriginalVestingResponse, Period}; use super::Account; impl VestingAccount for Account { - fn total_pledged_locked( - &self, - storage: &dyn Storage, - env: &Env, - ) -> Result { - Ok(self.get_delegated_vesting(None, env, storage)?.amount - + self.get_pledged_vesting(None, env, storage)?.amount) + fn total_staked(&self, storage: &dyn Storage) -> Result { + Ok(self.total_delegations(storage)? + self.total_pledged(storage)?) } /// See [VestingAccount::locked_coins] for documentation. /// Returns 0 in case of underflow. Which is fine, as the amount of pledged and delegated tokens can be larger then vesting_coins due to rewards and vesting periods expiring + + // TODO: rename. it's no longer 'locked'... or is it? fn locked_coins( &self, block_time: Option, env: &Env, storage: &dyn Storage, ) -> Result { + let still_vesting = self.get_vesting_coins(block_time, env, storage)?.amount; + let staked = self.total_staked(storage)?; + let locked_amount = still_vesting.saturating_sub(staked); + Ok(Coin { - amount: Uint128::new( - self.get_vesting_coins(block_time, env, storage)? - .amount - .u128() - .saturating_sub( - self.get_delegated_vesting(block_time, env, storage)? - .amount - .u128(), - ) - .saturating_sub( - self.get_pledged_vesting(block_time, env, storage)? - .amount - .u128(), - ), - ), + amount: locked_amount, denom: MIX_DENOM.load(storage)?, }) } @@ -51,15 +39,54 @@ impl VestingAccount for Account { storage: &dyn Storage, ) -> Result { Ok(Coin { - amount: Uint128::new( - self.load_balance(storage)? - .u128() - .saturating_sub(self.locked_coins(block_time, env, storage)?.amount.u128()), - ), + amount: self + .load_balance(storage)? + .saturating_sub(self.locked_coins(block_time, env, storage)?.amount), denom: MIX_DENOM.load(storage)?, }) } + fn spendable_vested_coins( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result { + let vested = self.get_vested_coins(block_time, env, storage)?; + let withdrawn = self.load_withdrawn(storage)?; + + let not_withdrawn_vested = vested.amount.saturating_sub(withdrawn); + if not_withdrawn_vested == Uint128::zero() { + return Ok(Coin { + denom: vested.denom, + amount: Uint128::zero(), + }); + } + let spendable = self.spendable_coins(block_time, env, storage)?; + + Ok(Coin { + denom: spendable.denom, + // TODO: actually, is it even possible for spendable > not_withdrawn_vested? + amount: min(spendable.amount, not_withdrawn_vested), + }) + } + + fn spendable_reward_coins( + &self, + block_time: Option, + env: &Env, + storage: &dyn Storage, + ) -> Result { + let spendable = self.spendable_coins(block_time, env, storage)?; + let spendable_vested = self.spendable_vested_coins(block_time, env, storage)?; + + Ok(Coin { + denom: spendable.denom, + // don't use saturating subs as those should never fail, thus return an error + amount: spendable.amount.checked_sub(spendable_vested.amount)?, + }) + } + fn get_vested_coins( &self, block_time: Option, @@ -105,7 +132,7 @@ impl VestingAccount for Account { } fn get_end_time(&self) -> Timestamp { - self.periods[(self.num_vesting_periods() - 1)].end_time() + self.periods[self.num_vesting_periods() - 1].end_time() } fn get_original_vesting(&self) -> Result { @@ -116,124 +143,12 @@ impl VestingAccount for Account { )) } - fn get_delegated_free( - &self, - block_time: Option, - env: &Env, - storage: &dyn Storage, - ) -> Result { - let block_time = block_time.unwrap_or(env.block.time); - let period = self.get_current_vesting_period(block_time)?; - let withdrawn = self.load_withdrawn(storage)?; - let max_available = self - .get_vested_coins(Some(block_time), env, storage)? - .amount - .saturating_sub(withdrawn); - let start_time = match period { - Period::Before => 0, - Period::After => u64::MAX, - Period::In(idx) => self.periods[idx].start_time, - }; - - let coin = self.total_delegations_at_timestamp(storage, start_time)?; - - let amount = Uint128::new(coin.u128().min(max_available.u128())); - - Ok(Coin { - amount, - denom: MIX_DENOM.load(storage)?, - }) - } - - // TODO: why do we allow querying for block times in the past? - just use env.block.time all the time - fn get_delegated_vesting( - &self, - block_time: Option, - env: &Env, - storage: &dyn Storage, - ) -> Result { - let block_time = block_time.unwrap_or(env.block.time); - let delegated_free = self.get_delegated_free(Some(block_time), env, storage)?; - - let delegations_before_start_time = - self.total_delegations_at_timestamp(storage, block_time.seconds())?; - - let amount = delegations_before_start_time - delegated_free.amount; - - Ok(Coin { - amount, - denom: MIX_DENOM.load(storage)?, - }) - } - - fn get_pledged_free( - &self, - block_time: Option, - env: &Env, - storage: &dyn Storage, - ) -> Result { - let block_time = block_time.unwrap_or(env.block.time); - let period = self.get_current_vesting_period(block_time)?; - let max_vested = self.get_vested_coins(Some(block_time), env, storage)?; - let start_time = match period { - Period::Before => 0, - Period::After => u64::MAX, - Period::In(idx) => self.periods[idx].start_time, - }; - - let amount = if let Some(bond) = self - .load_mixnode_pledge(storage)? - .or(self.load_gateway_pledge(storage)?) - { - if bond.block_time().seconds() < start_time { - bond.amount().amount - } else { - Uint128::zero() - } - } else { - Uint128::zero() - }; - - let amount = Uint128::new(amount.u128().min(max_vested.amount.u128())); - - Ok(Coin { - amount, - denom: MIX_DENOM.load(storage)?, - }) - } - - fn get_pledged_vesting( - &self, - block_time: Option, - env: &Env, - storage: &dyn Storage, - ) -> Result { - let block_time = block_time.unwrap_or(env.block.time); - let bonded_free = self.get_pledged_free(Some(block_time), env, storage)?; - - if let Some(bond) = self - .load_mixnode_pledge(storage)? - .or(self.load_gateway_pledge(storage)?) - { - let amount = bond.amount().amount - bonded_free.amount; - Ok(Coin { - amount, - denom: MIX_DENOM.load(storage)?, - }) - } else { - Ok(Coin { - amount: Uint128::zero(), - denom: MIX_DENOM.load(storage)?, - }) - } - } - fn transfer_ownership( &mut self, to_address: &Addr, storage: &mut dyn Storage, ) -> Result<(), ContractError> { - delete_account(&self.owner_address(), storage)?; + delete_account(self.owner_address(), storage)?; self.owner_address = to_address.to_owned(); save_account(self, storage)?; Ok(()) @@ -245,7 +160,7 @@ impl VestingAccount for Account { storage: &mut dyn Storage, ) -> Result<(), ContractError> { if let Some(staking_address) = self.staking_address() { - delete_account(staking_address, storage)?; + delete_account(staking_address.to_owned(), storage)?; } self.staking_address = to_address; save_account(self, storage)?; @@ -258,4 +173,28 @@ impl VestingAccount for Account { self.save_balance(new_balance, storage)?; Ok(()) } + + // balance consists of: + // - original vesting amount + // - minus what you have staked + // - minus what you have withdrawn + // - plus whatever reward you have claimed + // thus rewards = (balance + withdrawn + staked) - original vesting + fn get_historical_vested_staking_rewards( + &self, + storage: &dyn Storage, + ) -> Result { + let balance = self.load_balance(storage)?; + let withdrawn = self.load_withdrawn(storage)?; + let staked = self.total_staked(storage)?; + let original = &self.coin; + let total = balance + withdrawn + staked; + + let rewards = Coin { + denom: original.denom.clone(), + amount: total.checked_sub(original.amount)?, + }; + + Ok(rewards) + } } diff --git a/contracts/vesting/src/vesting/mod.rs b/contracts/vesting/src/vesting/mod.rs index 3eb6134e4e..bd50838175 100644 --- a/contracts/vesting/src/vesting/mod.rs +++ b/contracts/vesting/src/vesting/mod.rs @@ -37,6 +37,7 @@ pub fn populate_vesting_periods( #[cfg(test)] mod tests { use crate::contract::*; + use crate::errors::ContractError; use crate::storage::*; use crate::support::tests::helpers::vesting_account_percent_fixture; use crate::support::tests::helpers::{ @@ -47,7 +48,7 @@ mod tests { use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount}; use crate::vesting::{populate_vesting_periods, Account}; use cosmwasm_std::testing::{mock_env, mock_info}; - use cosmwasm_std::{coins, Addr, Coin, Timestamp, Uint128}; + use cosmwasm_std::{coin, coins, Addr, Coin, Timestamp, Uint128}; use mixnet_contract_common::mixnode::MixNodeCostParams; use mixnet_contract_common::{Gateway, MixNode, Percent}; use vesting_contract_common::messages::{ExecuteMsg, VestingSpecification}; @@ -71,21 +72,44 @@ mod tests { let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM)); let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); - let created_account = load_account(&Addr::unchecked("owner"), &deps.storage) + let created_account = load_account(Addr::unchecked("owner"), &deps.storage) .unwrap() .unwrap(); - let created_account_test_by_staking = - load_account(&Addr::unchecked("staking"), &deps.storage) - .unwrap() - .unwrap(); - assert_eq!(created_account_test_by_staking, created_account); + assert_eq!( created_account.load_balance(&deps.storage).unwrap(), // One was liquidated Uint128::new(1_000_000_000_000) ); + + // nothing is saved for "staking" account! + let created_account_test_by_staking = + load_account(Addr::unchecked("staking"), &deps.storage).unwrap(); + assert!(created_account_test_by_staking.is_none()); + + // but we can stake on its behalf! + let stake_msg = ExecuteMsg::DelegateToMixnode { + on_behalf_of: Some("owner".to_string()), + mix_id: 42, + amount: coin(500, TEST_COIN_DENOM), + }; + + let response = execute( + deps.as_mut(), + env.clone(), + mock_info("staking", &[]), + stake_msg, + ); + assert!(response.is_ok()); + + assert_eq!( + created_account.load_balance(&deps.storage).unwrap(), + // One was liquidated + Uint128::new(999_999_999_500) + ); + // Try create the same account again - let _response = execute(deps.as_mut(), env.clone(), info, msg.clone()); + let response = execute(deps.as_mut(), env.clone(), info, msg); assert!(response.is_err()); let account_again = vesting_account_new_fixture(&mut deps.storage, &env); @@ -99,11 +123,12 @@ mod tests { let mut env = mock_env(); let info = mock_info("owner", &[]); let account = vesting_account_new_fixture(&mut deps.storage, &env); + let staker = account.staking_address().unwrap(); let msg = ExecuteMsg::TransferOwnership { to_address: "new_owner".to_string(), }; let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap(); - let new_owner_account = load_account(&Addr::unchecked("new_owner"), &deps.storage) + let new_owner_account = load_account(Addr::unchecked("new_owner"), &deps.storage) .unwrap() .unwrap(); assert_eq!( @@ -112,25 +137,45 @@ mod tests { ); // Check old account is gone - let old_owner_account = load_account(&Addr::unchecked("owner"), &deps.storage).unwrap(); + let old_owner_account = load_account(Addr::unchecked("owner"), &deps.storage).unwrap(); assert!(old_owner_account.is_none()); // Not the owner let response = execute(deps.as_mut(), env.clone(), info.clone(), msg); assert!(response.is_err()); + // can't stake on behalf of the original owner anymore, but we can do it for the new one! + let stake_msg = ExecuteMsg::DelegateToMixnode { + on_behalf_of: Some("owner".to_string()), + mix_id: 42, + amount: coin(500, TEST_COIN_DENOM), + }; + let response = execute( + deps.as_mut(), + env.clone(), + mock_info(staker.as_ref(), &[]), + stake_msg, + ); + assert!(response.is_err()); + + let new_stake_msg = ExecuteMsg::DelegateToMixnode { + on_behalf_of: Some("new_owner".to_string()), + mix_id: 42, + amount: coin(500, TEST_COIN_DENOM), + }; + let response = execute( + deps.as_mut(), + env.clone(), + mock_info(staker.as_ref(), &[]), + new_stake_msg, + ); + assert!(response.is_ok()); + let info = mock_info("new_owner", &[]); let msg = ExecuteMsg::UpdateStakingAddress { to_address: Some("new_staking".to_string()), }; let _response = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - let new_staking_account = load_account(&Addr::unchecked("new_staking"), &deps.storage) - .unwrap() - .unwrap(); - assert_eq!(new_staking_account.owner_address(), "new_owner".to_string()); - - let old_staking_account = load_account(&Addr::unchecked("staking"), &deps.storage).unwrap(); - assert!(old_staking_account.is_none()); let msg = ExecuteMsg::WithdrawVestedCoins { amount: Coin { @@ -172,6 +217,117 @@ mod tests { assert!(response.is_err()); } + #[test] + fn test_staking_address_change() { + let mut deps = init_contract(); + let env = mock_env(); + let account = vesting_account_new_fixture(&mut deps.storage, &env); + let original_staker = account.staking_address().unwrap(); + + // can stake on behalf without an issue + let stake_msg = ExecuteMsg::DelegateToMixnode { + on_behalf_of: Some("owner".to_string()), + mix_id: 42, + amount: coin(500, TEST_COIN_DENOM), + }; + let response = execute( + deps.as_mut(), + env.clone(), + mock_info(original_staker.as_ref(), &[]), + stake_msg.clone(), + ); + assert!(response.is_ok()); + + let info = mock_info("owner", &[]); + let msg = ExecuteMsg::UpdateStakingAddress { + to_address: Some("new_staking".to_string()), + }; + let _response = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + + // the old staking account can't do any staking anymore! + let response = execute( + deps.as_mut(), + env.clone(), + mock_info(original_staker.as_ref(), &[]), + stake_msg.clone(), + ); + assert!(response.is_err()); + + // but the new one can + let response = execute( + deps.as_mut(), + env.clone(), + mock_info("new_staking", &[]), + stake_msg, + ); + assert!(response.is_ok()); + } + + #[test] + fn test_staking_account_transfer() { + let mut deps = init_contract(); + let env = mock_env(); + + let amount1 = coin(1000000000, "unym"); + let amount2 = coin(100, "unym"); + + // create the accounts + let msg1 = ExecuteMsg::CreateAccount { + owner_address: "vesting1".to_string(), + staking_address: None, + vesting_spec: None, + cap: None, + }; + let res1 = execute( + deps.as_mut(), + env.clone(), + mock_info("admin", &[amount1.clone()]), + msg1, + ); + assert!(res1.is_ok()); + + let msg2 = ExecuteMsg::CreateAccount { + owner_address: "vesting2".to_string(), + staking_address: None, + vesting_spec: None, + cap: None, + }; + let res2 = execute( + deps.as_mut(), + env.clone(), + mock_info("admin", &[amount2.clone()]), + msg2, + ); + assert!(res2.is_ok()); + + let vesting1 = try_get_vesting_coins("vesting1", None, env.clone(), deps.as_ref()).unwrap(); + assert_eq!(vesting1, amount1); + + let vesting2 = try_get_vesting_coins("vesting2", None, env.clone(), deps.as_ref()).unwrap(); + assert_eq!(vesting2, amount2); + + let staking_address_change = ExecuteMsg::UpdateStakingAddress { + to_address: Some("vesting1".to_string()), + }; + let res = execute( + deps.as_mut(), + env.clone(), + mock_info("vesting2", &[]), + staking_address_change, + ); + assert_eq!( + Err(ContractError::StakingAccountExists("vesting1".to_string())), + res + ); + + // ensure nothing has changed! + let vesting1 = try_get_vesting_coins("vesting1", None, env.clone(), deps.as_ref()).unwrap(); + assert_eq!(vesting1, amount1); + + let vesting2 = try_get_vesting_coins("vesting2", None, env, deps.as_ref()).unwrap(); + assert_eq!(vesting2, amount2); + } + #[test] fn test_period_logic() { let mut deps = init_contract(); @@ -305,13 +461,11 @@ mod tests { let vesting_coins = account .get_vesting_coins(None, &env, &deps.storage) .unwrap(); - let locked_coins = account.locked_coins(None, &env, &mut deps.storage).unwrap(); + let locked_coins = account.locked_coins(None, &env, &deps.storage).unwrap(); assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000)); assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000)); assert_eq!(locked_coins.amount, Uint128::new(750_000_000_000)); - let spendable = account - .spendable_coins(None, &env, &mut deps.storage) - .unwrap(); + let spendable = account.spendable_coins(None, &env, &deps.storage).unwrap(); assert_eq!(spendable.amount, Uint128::new(250_000_000_000)); let withdrawn = account.load_withdrawn(&deps.storage).unwrap(); assert_eq!(withdrawn, Uint128::zero()); @@ -334,40 +488,24 @@ mod tests { assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000)); assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000)); - let delegated_free = account - .get_delegated_free(None, &env, &mut deps.storage) - .unwrap(); - let delegated_vesting = account - .get_delegated_vesting(None, &env, &mut deps.storage) - .unwrap(); + let delegated = account.total_delegations(&deps.storage).unwrap(); + assert_eq!(delegated, Uint128::new(90_000_000_000)); - assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000)); - assert_eq!(delegated_vesting.amount, Uint128::zero()); + let locked_coins = account.locked_coins(None, &env, &deps.storage).unwrap(); + // vesting - delegated + assert_eq!(locked_coins.amount, Uint128::new(660_000_000_000)); + let spendable = account.spendable_coins(None, &env, &deps.storage).unwrap(); + assert_eq!(spendable.amount, Uint128::new(250_000_000_000)); - let locked_coins = account.locked_coins(None, &env, &mut deps.storage).unwrap(); - // vesting - delegated_vesting - pledged_vesting - assert_eq!(locked_coins.amount, Uint128::new(750_000_000_000)); - let spendable = account - .spendable_coins(None, &env, &mut deps.storage) - .unwrap(); - assert_eq!(spendable.amount, Uint128::new(160_000_000_000)); - - let ok = account.try_undelegate_from_mixnode(mix_id, &mut deps.storage); + let ok = account.try_undelegate_from_mixnode(mix_id, &deps.storage); assert!(ok.is_ok()); account .track_undelegation(mix_id, delegation.clone(), &mut deps.storage) .unwrap(); - let delegated_free = account - .get_delegated_free(None, &env, &mut deps.storage) - .unwrap(); - let delegated_vesting = account - .get_delegated_vesting(None, &env, &mut deps.storage) - .unwrap(); - - assert_eq!(delegated_free.amount, Uint128::zero()); - assert_eq!(delegated_vesting.amount, Uint128::zero()); + let delegated = account.total_delegations(&deps.storage).unwrap(); + assert_eq!(delegated, Uint128::zero()); assert_eq!( account.load_balance(&deps.storage).unwrap(), @@ -393,40 +531,29 @@ mod tests { let vesting_coins = account .get_vesting_coins(None, &env, &deps.storage) .unwrap(); - let locked_coins = account.locked_coins(None, &env, &mut deps.storage).unwrap(); + let locked_coins = account.locked_coins(None, &env, &deps.storage).unwrap(); assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000)); assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000)); assert_eq!(locked_coins.amount, Uint128::new(750_000_000_000)); - let spendable = account - .spendable_coins(None, &env, &mut deps.storage) - .unwrap(); + let spendable = account.spendable_coins(None, &env, &deps.storage).unwrap(); assert_eq!(spendable.amount, Uint128::zero()); - let ok = - account.try_delegate_to_mixnode(mix_id, delegation.clone(), &env, &mut deps.storage); + let ok = account.try_delegate_to_mixnode(mix_id, delegation, &env, &mut deps.storage); assert!(ok.is_ok()); let vested_coins = account.get_vested_coins(None, &env, &deps.storage).unwrap(); let vesting_coins = account .get_vesting_coins(None, &env, &deps.storage) .unwrap(); - let locked_coins = account.locked_coins(None, &env, &mut deps.storage).unwrap(); + let locked_coins = account.locked_coins(None, &env, &deps.storage).unwrap(); assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000)); assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000)); - let spendable = account - .spendable_coins(None, &env, &mut deps.storage) - .unwrap(); + let spendable = account.spendable_coins(None, &env, &deps.storage).unwrap(); assert_eq!(spendable.amount, Uint128::zero()); - let delegated_free = account - .get_delegated_free(None, &env, &mut deps.storage) - .unwrap(); - let delegated_vesting = account - .get_delegated_vesting(None, &env, &mut deps.storage) - .unwrap(); + let delegated = account.total_delegations(&deps.storage).unwrap(); - assert_eq!(delegated_free.amount, Uint128::zero()); - assert_eq!(delegated_vesting.amount, Uint128::new(90_000_000_000)); + assert_eq!(delegated, Uint128::new(90_000_000_000)); // vesting - delegated_vesting - pledged_vesting assert_eq!(locked_coins.amount, Uint128::new(660_000_000_000)); @@ -461,7 +588,7 @@ mod tests { let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM)); let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); - let account = load_account(&Addr::unchecked("owner"), &deps.storage) + let account = load_account(Addr::unchecked("owner"), &deps.storage) .unwrap() .unwrap(); @@ -517,70 +644,6 @@ mod tests { let total_delegations = account.total_delegations_for_mix(1, &deps.storage).unwrap(); assert_eq!(Uint128::new(90_000_000_000), total_delegations); - - let account = load_account(&Addr::unchecked("owner"), &deps.storage) - .unwrap() - .unwrap(); - - // Current period -> block_time: None - let delegated_free = account - .get_delegated_free(None, &env, &deps.storage) - .unwrap(); - assert_eq!(Uint128::new(0), delegated_free.amount); - - let delegated_vesting = account - .get_delegated_vesting(None, &env, &deps.storage) - .unwrap(); - assert_eq!( - account.total_delegations(&deps.storage).unwrap() - delegated_free.amount, - delegated_vesting.amount - ); - - // All periods - for (i, period) in account.periods().iter().enumerate() { - let delegated_free = account - .get_delegated_free( - Some(Timestamp::from_seconds(period.start_time + 1)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!( - (account.tokens_per_period().unwrap() * i as u128) - .min(account.total_delegations(&deps.storage).unwrap().u128()), - delegated_free.amount.u128() - ); - - let delegated_vesting = account - .get_delegated_vesting( - Some(Timestamp::from_seconds(period.start_time + 1)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!( - account.total_delegations(&deps.storage).unwrap() - delegated_free.amount, - delegated_vesting.amount - ); - } - - let delegated_free = account - .get_delegated_free( - Some(Timestamp::from_seconds(1764416964)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!(total_delegations, delegated_free.amount); - - let delegated_free = account - .get_delegated_vesting( - Some(Timestamp::from_seconds(1764416964)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!(Uint128::zero(), delegated_free.amount); } #[test] @@ -653,64 +716,6 @@ mod tests { let pledge = account.load_mixnode_pledge(&deps.storage).unwrap().unwrap(); assert_eq!(Uint128::new(90_000_000_000), pledge.amount().amount); - - // Current period -> block_time: None - let bonded_free = account.get_pledged_free(None, &env, &deps.storage).unwrap(); - assert_eq!(Uint128::new(0), bonded_free.amount); - - let bonded_vesting = account - .get_pledged_vesting(None, &env, &deps.storage) - .unwrap(); - assert_eq!( - pledge.amount().amount - bonded_free.amount, - bonded_vesting.amount - ); - - // All periods - for (i, period) in account.periods().iter().enumerate() { - let bonded_free = account - .get_pledged_free( - Some(Timestamp::from_seconds(period.start_time + 1)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!( - (account.tokens_per_period().unwrap() * i as u128) - .min(pledge.amount().amount.u128()), - bonded_free.amount.u128() - ); - - let bonded_vesting = account - .get_pledged_vesting( - Some(Timestamp::from_seconds(period.start_time + 1)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!( - pledge.amount().amount - bonded_free.amount, - bonded_vesting.amount - ); - } - - let bonded_free = account - .get_pledged_free( - Some(Timestamp::from_seconds(1764416964)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!(pledge.amount().amount, bonded_free.amount); - - let bonded_vesting = account - .get_pledged_vesting( - Some(Timestamp::from_seconds(1764416964)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!(Uint128::zero(), bonded_vesting.amount); } #[test] @@ -773,68 +778,10 @@ mod tests { let pledge = account.load_gateway_pledge(&deps.storage).unwrap().unwrap(); assert_eq!(Uint128::new(90_000_000_000), pledge.amount().amount); - - // Current period -> block_time: None - let bonded_free = account.get_pledged_free(None, &env, &deps.storage).unwrap(); - assert_eq!(Uint128::new(0), bonded_free.amount); - - let bonded_vesting = account - .get_pledged_vesting(None, &env, &deps.storage) - .unwrap(); - assert_eq!( - pledge.amount().amount - bonded_free.amount, - bonded_vesting.amount - ); - - // All periods - for (i, period) in account.periods().iter().enumerate() { - let bonded_free = account - .get_pledged_free( - Some(Timestamp::from_seconds(period.start_time + 1)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!( - (account.tokens_per_period().unwrap() * i as u128) - .min(pledge.amount().amount.u128()), - bonded_free.amount.u128() - ); - - let bonded_vesting = account - .get_pledged_vesting( - Some(Timestamp::from_seconds(period.start_time + 1)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!( - pledge.amount().amount - bonded_free.amount, - bonded_vesting.amount - ); - } - - let bonded_free = account - .get_pledged_free( - Some(Timestamp::from_seconds(1764416964)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!(pledge.amount().amount, bonded_free.amount); - - let bonded_vesting = account - .get_pledged_vesting( - Some(Timestamp::from_seconds(1764416964)), - &env, - &deps.storage, - ) - .unwrap(); - assert_eq!(Uint128::zero(), bonded_vesting.amount); } #[test] - fn delegated_free() { + fn test_delegations_cap() { let mut deps = init_contract(); let mut env = mock_env(); @@ -843,37 +790,14 @@ mod tests { let account_creation_timestamp = 1650000000; let account_creation_blockheight = 12345; - // this value is completely arbitrary, I just wanted to keep consistent - // (and make sure that if block timestamp increases so does the block height) - let blocks_per_period = 100; - env.block.height = account_creation_blockheight; env.block.time = Timestamp::from_seconds(account_creation_timestamp); // lets define some helper timestamps - // our account is set to be created after 2 vesting periods already passed - let vesting_start_blockheight = account_creation_blockheight - 2 * blocks_per_period; - let vesting_start_timestamp = account_creation_timestamp - 2 * vesting_period_length_secs; - - let vesting_period2_start_blockheight = vesting_start_blockheight + blocks_per_period; - let vesting_period2_start_timestamp = vesting_start_timestamp + vesting_period_length_secs; - - // this vesting period is currently in progress! - let vesting_period3_start_blockheight = - vesting_period2_start_blockheight + blocks_per_period; - let vesting_period3_start_timestamp = - vesting_period2_start_timestamp + vesting_period_length_secs; - - // and this one is in the future! (in relation to account creation) - let vesting_period4_start_blockheight = - vesting_period3_start_blockheight + blocks_per_period; - let vesting_period4_start_timestamp = - vesting_period3_start_timestamp + vesting_period_length_secs; - // lets create our vesting account let periods = populate_vesting_periods( - vesting_start_timestamp, + account_creation_timestamp, VestingSpecification::new(None, Some(vesting_period_length_secs), None), ); @@ -896,108 +820,34 @@ mod tests { let mix_id = 42; let delegation = Coin { - amount: Uint128::new(90_000_000_000), + amount: Uint128::new(42), denom: TEST_COIN_DENOM.to_string(), }; - // delegate explicitly at the time the account was created - // (i.e. after 2 vesting periods already elapsed) - env.block.height = account_creation_blockheight; - env.block.time = Timestamp::from_seconds(account_creation_timestamp); - let ok = vesting_account.try_delegate_to_mixnode( - mix_id, - delegation.clone(), - &env, - &mut deps.storage, + // you can have at most `MAX_PER_MIX_DELEGATIONS` delegations so those should be fine + for _ in 0..MAX_PER_MIX_DELEGATIONS { + vesting_account + .try_delegate_to_mixnode(mix_id, delegation.clone(), &env, &mut deps.storage) + .unwrap(); + + env.block.height += 1; + env.block.time = env.block.time.plus_seconds(42); + } + + // but the additional one is going to fail + let res = vesting_account + .try_delegate_to_mixnode(mix_id, delegation.clone(), &env, &mut deps.storage) + .unwrap_err(); + + assert_eq!( + res, + ContractError::TooManyDelegations { + address: vesting_account.owner_address(), + acc_id: vesting_account.storage_key(), + mix_id, + num: MAX_PER_MIX_DELEGATIONS, + cap: MAX_PER_MIX_DELEGATIONS + } ); - assert!(ok.is_ok()); - - let vested_coins = vesting_account - .get_vested_coins(None, &env, &deps.storage) - .unwrap(); - let vesting_coins = vesting_account - .get_vesting_coins(None, &env, &deps.storage) - .unwrap(); - assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000)); - assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000)); - let delegated_free = vesting_account - .get_delegated_free(None, &env, &deps.storage) - .unwrap(); - let delegated_vesting = vesting_account - .get_delegated_vesting(None, &env, &deps.storage) - .unwrap(); - - // all good so far - assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000)); - assert_eq!(delegated_vesting.amount, Uint128::zero()); - - // some time passes, and we're now into the next vesting period, more of our coins got unlocked! - env.block.height = vesting_period4_start_blockheight; - env.block.time = Timestamp::from_seconds(vesting_period4_start_timestamp); - - let vested_coins = vesting_account - .get_vested_coins(None, &env, &deps.storage) - .unwrap(); - let vesting_coins = vesting_account - .get_vesting_coins(None, &env, &deps.storage) - .unwrap(); - assert_eq!(vested_coins.amount, Uint128::new(375_000_000_000)); - assert_eq!(vesting_coins.amount, Uint128::new(625_000_000_000)); - - // and nothing about our existing delegation changed - let delegated_free = vesting_account - .get_delegated_free(None, &env, &deps.storage) - .unwrap(); - let delegated_vesting = vesting_account - .get_delegated_vesting(None, &env, &deps.storage) - .unwrap(); - assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000)); - assert_eq!(delegated_vesting.amount, Uint128::zero()); - - // however, create a new delegation now in this brand new vesting period - let delegation = Coin { - amount: Uint128::new(50_000_000_000), - denom: TEST_COIN_DENOM.to_string(), - }; - let ok = vesting_account.try_delegate_to_mixnode( - mix_id, - delegation.clone(), - &env, - &mut deps.storage, - ); - assert!(ok.is_ok()); - - // we're still good here, we have delegated in total 140M from our vested tokens! - let delegated_free = vesting_account - .get_delegated_free(None, &env, &deps.storage) - .unwrap(); - let delegated_vesting = vesting_account - .get_delegated_vesting(None, &env, &deps.storage) - .unwrap(); - assert_eq!(delegated_free.amount, Uint128::new(140_000_000_000)); - assert_eq!(delegated_vesting.amount, Uint128::zero()); - - // but let's ask now a different question: - // how many vested tokens have I had delegated during vesting period3? (i.e. after account creation) - let delegated_free = vesting_account - .get_delegated_free( - Some(Timestamp::from_seconds(vesting_period3_start_timestamp)), - &env, - &deps.storage, - ) - .unwrap(); - let delegated_vesting = vesting_account - .get_delegated_vesting( - Some(Timestamp::from_seconds(vesting_period3_start_timestamp)), - &env, - &deps.storage, - ) - .unwrap(); - - // returns 90M as the 50M delegation didn't exist at this point of time - assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000)); - - // the 50M delegation wasn't a thing here for VESTING tokens either - assert_eq!(delegated_vesting.amount, Uint128::zero()); } } diff --git a/docker-compose.yml b/docker-compose.yml index 6c756e2ebd..7108925d69 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,7 @@ services: container_name: genesis_validator volumes: - "genesis_volume:/genesis_volume" - - "genesis_nymd:/root/.nymd" + - "genesis_nyxd:/root/.nyxd" environment: *NETWORK networks: localnet: @@ -34,7 +34,7 @@ services: - "2317:1317" volumes: - "genesis_volume:/genesis_volume" - - "secondary_nymd:/root/.nymd" + - "secondary_nyxd:/root/.nyxd" environment: *NETWORK networks: localnet: @@ -120,8 +120,8 @@ services: volumes: genesis_volume: - genesis_nymd: - secondary_nymd: + genesis_nyxd: + secondary_nyxd: # contract_volume: # mongo_data: diff --git a/docker/typescript_client/upload_contract/package-lock.json b/docker/typescript_client/upload_contract/package-lock.json index a87087b263..5afb3c4577 100644 --- a/docker/typescript_client/upload_contract/package-lock.json +++ b/docker/typescript_client/upload_contract/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@nymproject/nym-validator-client": "0.18.0", "@types/node": "^14.14.22", - "nodemon": "^2.0.7", + "nodemon": "^2.0.20", "save-dev": "0.0.1-security", "tasktimer": "^3.0.0", "ts-node": "^9.1.1" @@ -309,25 +309,6 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@tsconfig/recommended": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@tsconfig/recommended/-/recommended-1.0.1.tgz", @@ -349,81 +330,6 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "node_modules/ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "dependencies": { - "string-width": "^3.0.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -507,27 +413,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, - "node_modules/boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -558,84 +443,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/chokidar": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", @@ -656,11 +463,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, "node_modules/cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -670,61 +472,10 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dependencies": { - "mimic-response": "^1.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/create-hash": { "version": "1.2.0", @@ -756,14 +507,6 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "engines": { - "node": ">=8" - } - }, "node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -772,30 +515,6 @@ "ms": "^2.1.1" } }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -815,22 +534,6 @@ "node": ">=0.3.1" } }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, "node_modules/elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -845,27 +548,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "engines": { - "node": ">=8" - } - }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -919,17 +601,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -941,20 +612,6 @@ "node": ">= 6" } }, - "node_modules/global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", - "dependencies": { - "ini": "1.3.7" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globalthis": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz", @@ -969,32 +626,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1003,14 +634,6 @@ "node": ">=4" } }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "engines": { - "node": ">=8" - } - }, "node_modules/hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", @@ -1043,42 +666,16 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" - }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "engines": { - "node": ">=4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "engines": { - "node": ">=0.8.19" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1090,17 +687,6 @@ "node": ">=8" } }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1109,14 +695,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -1128,29 +706,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "dependencies": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "engines": { - "node": ">=8" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -1159,32 +714,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" - }, "node_modules/isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", @@ -1198,30 +727,6 @@ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" - }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dependencies": { - "json-buffer": "3.0.0" - } - }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/libsodium": { "version": "0.7.10", "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz", @@ -1240,36 +745,6 @@ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -1285,14 +760,6 @@ "safe-buffer": "^5.1.2" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "engines": { - "node": ">=4" - } - }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -1304,9 +771,9 @@ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1314,32 +781,26 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nodemon": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz", - "integrity": "sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA==", - "hasInstallScript": true, + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", "dependencies": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", + "chokidar": "^3.5.2", + "debug": "^3.2.7", "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.3", - "update-notifier": "^4.1.0" + "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" @@ -1374,14 +835,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "engines": { - "node": ">=8" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -1390,44 +843,6 @@ "node": ">= 0.4" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/pako": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", @@ -1459,14 +874,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "engines": { - "node": ">=4" - } - }, "node_modules/prettier": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", @@ -1514,26 +921,6 @@ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -1542,20 +929,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -1585,36 +958,6 @@ "resolved": "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz", "integrity": "sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==" }, - "node_modules/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -1656,25 +999,6 @@ "semver": "bin/semver" } }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -1687,10 +1011,24 @@ "sha.js": "bin.js" } }, - "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "bin": { + "semver": "bin/semver.js" + } }, "node_modules/source-map": { "version": "0.6.1", @@ -1717,38 +1055,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1776,25 +1082,6 @@ "eventemitter3": "^4.0.0" } }, - "node_modules/term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1842,22 +1129,6 @@ "typescript": ">=2.7" } }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/typescript": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", @@ -1871,106 +1142,15 @@ } }, "node_modules/undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "dependencies": { - "debug": "^2.2.0" - } - }, - "node_modules/undefsafe/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/undefsafe/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "dependencies": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "node_modules/ws": { "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", @@ -1991,14 +1171,6 @@ } } }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "engines": { - "node": ">=8" - } - }, "node_modules/xstream": { "version": "11.14.0", "resolved": "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz", @@ -2266,19 +1438,6 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "requires": { - "defer-to-connect": "^1.0.1" - } - }, "@tsconfig/recommended": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@tsconfig/recommended/-/recommended-1.0.1.tgz", @@ -2300,62 +1459,6 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "requires": { - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -2421,21 +1524,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, - "boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2463,64 +1551,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - } - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "chokidar": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", @@ -2536,11 +1566,6 @@ "readdirp": "~3.6.0" } }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -2550,49 +1575,10 @@ "safe-buffer": "^5.0.1" } }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "create-hash": { "version": "1.2.0", @@ -2624,11 +1610,6 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" - }, "debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -2637,24 +1618,6 @@ "ms": "^2.1.1" } }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2668,19 +1631,6 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "requires": { - "is-obj": "^2.0.0" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, "elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -2695,24 +1645,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" - }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -2742,14 +1674,6 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "optional": true }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -2758,14 +1682,6 @@ "is-glob": "^4.0.1" } }, - "global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", - "requires": { - "ini": "1.3.7" - } - }, "globalthis": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz", @@ -2774,39 +1690,11 @@ "define-properties": "^1.1.3" } }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" - }, "hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", @@ -2836,36 +1724,16 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" - }, "ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" - }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" - }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2874,24 +1742,11 @@ "binary-extensions": "^2.0.0" } }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "requires": { - "ci-info": "^2.0.0" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -2900,45 +1755,11 @@ "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "requires": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - } - }, - "is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==" - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" - }, "isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", @@ -2950,27 +1771,6 @@ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "requires": { - "json-buffer": "3.0.0" - } - }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "requires": { - "package-json": "^6.3.0" - } - }, "libsodium": { "version": "0.7.10", "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz", @@ -2989,26 +1789,6 @@ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -3024,11 +1804,6 @@ "safe-buffer": "^5.1.2" } }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -3040,38 +1815,33 @@ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "nodemon": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz", - "integrity": "sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", + "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", "requires": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", + "chokidar": "^3.5.2", + "debug": "^3.2.7", "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.3", - "update-notifier": "^4.1.0" + "undefsafe": "^2.0.5" } }, "nopt": { @@ -3087,47 +1857,11 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" - }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" - }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, "pako": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", @@ -3150,11 +1884,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" - }, "prettier": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", @@ -3193,23 +1922,6 @@ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "requires": { - "escape-goat": "^2.0.0" - } - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -3218,17 +1930,6 @@ "safe-buffer": "^5.1.0" } }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -3252,30 +1953,6 @@ "resolved": "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz", "integrity": "sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==" }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "requires": { - "rc": "^1.2.8" - } - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "requires": { - "lowercase-keys": "^1.0.0" - } - }, "ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -3300,21 +1977,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -3324,10 +1986,20 @@ "safe-buffer": "^5.0.1" } }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + "simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" + } + } }, "source-map": { "version": "0.6.1", @@ -3351,29 +2023,6 @@ "safe-buffer": "~5.2.0" } }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -3395,16 +2044,6 @@ "eventemitter3": "^4.0.0" } }, - "term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==" - }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3434,123 +2073,27 @@ "yn": "3.1.1" } }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "requires": { - "is-typedarray": "^1.0.0" - } - }, "typescript": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==" }, "undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "requires": { - "debug": "^2.2.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "requires": { - "crypto-random-string": "^2.0.0" - } - }, - "update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "requires": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - } - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "requires": { - "prepend-http": "^2.0.0" - } + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "requires": { - "string-width": "^4.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "ws": { "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", "requires": {} }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" - }, "xstream": { "version": "11.14.0", "resolved": "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz", diff --git a/docker/typescript_client/upload_contract/package.json b/docker/typescript_client/upload_contract/package.json index b52590cc53..7ae335c578 100644 --- a/docker/typescript_client/upload_contract/package.json +++ b/docker/typescript_client/upload_contract/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@types/node": "^14.14.22", - "nodemon": "^2.0.7", + "nodemon": "^2.0.20", "@nymproject/nym-validator-client" : "0.18.0", "save-dev": "0.0.1-security", "tasktimer": "^3.0.0", diff --git a/docker/validator/init_and_start.sh b/docker/validator/init_and_start.sh index 722e7224d2..a1e8820fbc 100755 --- a/docker/validator/init_and_start.sh +++ b/docker/validator/init_and_start.sh @@ -7,39 +7,39 @@ cd /root if [ "$1" = "genesis" ]; then if [ ! -f "/root/.nymd/config/genesis.json" ]; then - ./nymd init nymnet --chain-id nymnet 2> /dev/null + ./nyxd init nymnet --chain-id nymnet 2> /dev/null # staking/governance token is hardcoded in config, change this - sed -i "s/\"stake\"/\"u${STAKE_DENOM}\"/" /root/.nymd/config/genesis.json - sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025u'"${DENOM}"'"/' /root/.nymd/config/app.toml - sed -i '0,/enable = false/s//enable = true/g' /root/.nymd/config/app.toml - sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' /root/.nymd/config/config.toml - sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' /root/.nymd/config/config.toml - sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' /root/.nymd/config/config.toml + sed -i "s/\"stake\"/\"u${STAKE_DENOM}\"/" /root/.nyxd/config/genesis.json + sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025u'"${DENOM}"'"/' /root/.nyxd/config/app.toml + sed -i '0,/enable = false/s//enable = true/g' /root/.nyxd/config/app.toml + sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' /root/.nyxd/config/config.toml + sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' /root/.nyxd/config/config.toml + sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' /root/.nyxd/config/config.toml # create accounts - yes "${PASSPHRASE}" | ./nymd keys add node_admin 2>&1 >/dev/null | tail -n 1 > /root/.nymd/mnemonic - yes "${PASSPHRASE}" | ./nymd keys add secondary 2>&1 >/dev/null | tail -n 1 > /root/.nymd/secondary_mnemonic - cp /root/.nymd/mnemonic /genesis_volume/genesis_mnemonic - cp /root/.nymd/secondary_mnemonic /genesis_volume/secondary_mnemonic + yes "${PASSPHRASE}" | ./nyxd keys add node_admin 2>&1 >/dev/null | tail -n 1 > /root/.nyxd/mnemonic + yes "${PASSPHRASE}" | ./nyxd keys add secondary 2>&1 >/dev/null | tail -n 1 > /root/.nyxd/secondary_mnemonic + cp /root/.nyxd/mnemonic /genesis_volume/genesis_mnemonic + cp /root/.nyxd/secondary_mnemonic /genesis_volume/secondary_mnemonic # add genesis accounts with some initial tokens - GENESIS_ADDRESS=$(yes "${PASSPHRASE}" | ./nymd keys show node_admin -a) - SECONDARY_ADDRESS=$(yes "${PASSPHRASE}" | ./nymd keys show secondary -a) - yes "${PASSPHRASE}" | ./nymd add-genesis-account "${GENESIS_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}" - yes "${PASSPHRASE}" | ./nymd add-genesis-account "${SECONDARY_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}" + GENESIS_ADDRESS=$(yes "${PASSPHRASE}" | ./nyxd keys show node_admin -a) + SECONDARY_ADDRESS=$(yes "${PASSPHRASE}" | ./nyxd keys show secondary -a) + yes "${PASSPHRASE}" | ./nyxd add-genesis-account "${GENESIS_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}" + yes "${PASSPHRASE}" | ./nyxd add-genesis-account "${SECONDARY_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}" - yes "${PASSPHRASE}" | ./nymd gentx node_admin 1000000000u"${STAKE_DENOM}" --chain-id nymnet 2> /dev/null - ./nymd collect-gentxs 2> /dev/null - ./nymd validate-genesis > /dev/null - cp /root/.nymd/config/genesis.json /genesis_volume/genesis.json + yes "${PASSPHRASE}" | ./nyxd gentx node_admin 1000000000u"${STAKE_DENOM}" --chain-id nymnet 2> /dev/null + ./nyxd collect-gentxs 2> /dev/null + ./nyxd validate-genesis > /dev/null + cp /root/.nyxd/config/genesis.json /genesis_volume/genesis.json else echo "Validator already initialized, starting with the existing configuration." echo "If you want to re-init the validator, destroy the existing container" fi - ./nymd start + ./nyxd start elif [ "$1" = "secondary" ]; then if [ ! -f "/root/.nymd/config/genesis.json" ]; then - ./nymd init nymnet --chain-id nym-secondary 2> /dev/null + ./nyxd init nymnet --chain-id nym-secondary 2> /dev/null # Wait until the genesis node writes the genesis.json to the shared volume while ! [ -s /genesis_volume/genesis.json ]; do @@ -49,29 +49,29 @@ elif [ "$1" = "secondary" ]; then # wait for the actual validator to start up sleep 5 - cp /genesis_volume/genesis.json /root/.nymd/config/genesis.json - GENESIS_PEER=$(cat /root/.nymd/config/genesis.json | grep '"memo"' | cut -d'"' -f 4) - GENESIS_IP=$(cat /root/.nymd/config/genesis.json | grep '"memo"' | cut -d'@' -f2 | cut -d: -f1) - sed -i 's/persistent_peers = ""/persistent_peers = "'"${GENESIS_PEER}"'"/' /root/.nymd/config/config.toml - sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025u'"${BECH32_PREFIX}"'"/' /root/.nymd/config/app.toml - sed -i '0,/enable = false/s//enable = true/g' /root/.nymd/config/app.toml - sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' /root/.nymd/config/config.toml - sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' /root/.nymd/config/config.toml - sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' /root/.nymd/config/config.toml + cp /genesis_volume/genesis.json /root/.nyxd/config/genesis.json + GENESIS_PEER=$(cat /root/.nyxd/config/genesis.json | grep '"memo"' | cut -d'"' -f 4) + GENESIS_IP=$(cat /root/.nyxd/config/genesis.json | grep '"memo"' | cut -d'@' -f2 | cut -d: -f1) + sed -i 's/persistent_peers = ""/persistent_peers = "'"${GENESIS_PEER}"'"/' /root/.nyxd/config/config.toml + sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025u'"${BECH32_PREFIX}"'"/' /root/.nyxd/config/app.toml + sed -i '0,/enable = false/s//enable = true/g' /root/.nyxd/config/app.toml + sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' /root/.nyxd/config/config.toml + sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' /root/.nyxd/config/config.toml + sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' /root/.nyxd/config/config.toml # import mnemonic generated by the genesis validator (have a local copy for ease of use) - cp /genesis_volume/secondary_mnemonic /root/.nymd/mnemonic - { cat /root/.nymd/mnemonic; echo "${PASSPHRASE}"; echo "${PASSPHRASE}"; } | ./nymd keys add node_admin --recover #> /dev/null - ./nymd validate-genesis > /dev/null + cp /genesis_volume/secondary_mnemonic /root/.nyxd/mnemonic + { cat /root/.nyxd/mnemonic; echo "${PASSPHRASE}"; echo "${PASSPHRASE}"; } | ./nyxd keys add node_admin --recover #> /dev/null + ./nyxd validate-genesis > /dev/null # create validator # don't even ask about those sleeps... - { echo "${PASSPHRASE}"; sleep 10; yes; sleep 10; } | ./nymd tx staking create-validator --amount=10000000u"${STAKE_DENOM}" --fees 100000u"${DENOM}" --pubkey="$(./nymd tendermint show-validator)" --moniker="secondary" --commission-rate="0.10" --commission-max-rate="0.20" --commission-max-change-rate="0.01" --min-self-delegation="1" --chain-id=nymnet --from=node_admin -b async --node http://"${GENESIS_IP}":26657 + { echo "${PASSPHRASE}"; sleep 10; yes; sleep 10; } | ./nyxd tx staking create-validator --amount=10000000u"${STAKE_DENOM}" --fees 100000u"${DENOM}" --pubkey="$(./nyxd tendermint show-validator)" --moniker="secondary" --commission-rate="0.10" --commission-max-rate="0.20" --commission-max-change-rate="0.01" --min-self-delegation="1" --chain-id=nymnet --from=node_admin -b async --node http://"${GENESIS_IP}":26657 else echo "Validator already initialized, starting with the existing configuration." echo "If you want to re-init the validator, destroy the existing container" fi - ./nymd start + ./nyxd start else echo "Wrong command. Usage: ./$0 [genesis/secondary]" fi diff --git a/docker/validator/setup.sh b/docker/validator/setup.sh index efd40f0e14..4b176fb04e 100755 --- a/docker/validator/setup.sh +++ b/docker/validator/setup.sh @@ -8,7 +8,7 @@ git checkout "${WASMD_VERSION}" WASMD_COMMIT_HASH=$(git rev-parse HEAD) mkdir build go build \ - -o build/nymd -mod=readonly -tags "netgo,ledger" \ + -o build/nyxd -mod=readonly -tags "netgo,ledger" \ -ldflags "-X github.com/cosmos/cosmos-sdk/version.Name=nymd \ -X github.com/cosmos/cosmos-sdk/version.AppName=nymd \ -X github.com/CosmWasm/wasmd/app.NodeDir=.nymd \ diff --git a/envs/local.env b/envs/local.env new file mode 100644 index 0000000000..c7121762d0 --- /dev/null +++ b/envs/local.env @@ -0,0 +1,24 @@ +CONFIGURED=true + +RUST_LOG=info +RUST_BACKTRACE=1 + +BECH32_PREFIX=n +MIX_DENOM=unym +MIX_DENOM_DISPLAY=nym +STAKE_DENOM=unyx +STAKE_DENOM_DISPLAY=nyx +DENOMS_EXPONENT=6 +MIXNET_CONTRACT_ADDRESS=n1xsml6tm6pnx8nsuz3a54cznh55g6nuwu9ug9t92ucjkq5ewp5w9syxeqsv +VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav +BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 +COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1lp5zex6685kd22agzskhqsylpnssxnweyuvsz4edr4p4ta92qf3q3dhmsx +GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m +MULTISIG_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m +COCONUT_DKG_CONTRACT_ADDRESS=n1vwtgazgpancsfel04y7syc95ausmat47cjtldqzkdmx6phyrwx2qvkv32p +REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq +STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" +NYXD="http://127.0.0.1:26657" +NYM_API="http://127.0.0.1:8000" + +DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600" diff --git a/envs/mainnet.env b/envs/mainnet.env index 751081adb8..6d3d65f14f 100644 --- a/envs/mainnet.env +++ b/envs/mainnet.env @@ -13,9 +13,12 @@ MIXNET_CONTRACT_ADDRESS=n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0 VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 +GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 COCONUT_DKG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090" -NYMD_VALIDATOR="https://rpc.nymtech.net"; -API_VALIDATOR="https://validator.nymtech.net/api/" +NYXD="https://rpc.nymtech.net"; +NYM_API="https://validator.nymtech.net/api/" + +DKG_TIME_CONFIGURATION="259200,300,300,60,60,1209600" diff --git a/envs/qa-qwerty.env b/envs/qa-qwerty.env index 3f37a02385..474bf8d073 100644 --- a/envs/qa-qwerty.env +++ b/envs/qa-qwerty.env @@ -12,10 +12,13 @@ DENOMS_EXPONENT=6 MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 -COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n12ckdkm3q7eytefs7rwu4ue3t9hxgvl9v08jddmtwgct2ve0pv50q0t8dlt -MULTISIG_CONTRACT_ADDRESS=n14krxe8ukzagwhvec0rmteexu62w8k9kp9sra9ww6em2hnmzcukqsa0utc8 -COCONUT_DKG_CONTRACT_ADDRESS=n1rl5n6cxuz2hdy3f7d9hsnw8zn0zwwwr0r4dxfz7tktgpgkcnz9zshmvksc +COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19d2nwj7fdhxqmyvgy8lf3ad49a6vmww4shryhrkj2mqk36att66s6xzszw +GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm28l5 +MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy +COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090" -NYMD_VALIDATOR="https://qwerty-validator.qa.nymte.ch/" -API_VALIDATOR="https://qwerty-validator-api.qa.nymte.ch/api" +NYXD="https://qwerty-validator.qa.nymte.ch/" +NYM_API="https://qwerty-validator-api.qa.nymte.ch/api" + +DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600" diff --git a/envs/qa.env b/envs/qa.env index 64511fa922..a5442b7600 100644 --- a/envs/qa.env +++ b/envs/qa.env @@ -13,9 +13,12 @@ MIXNET_CONTRACT_ADDRESS=n1rjzps6qrmdqmf0xz4cn4x4rcmqeqzq6hnzqg4wcvd0r2lyasdq5sep VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw +GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m MULTISIG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs COCONUT_DKG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" -NYMD_VALIDATOR="https://qa-validator.nymtech.net" -API_VALIDATOR="https://qa-validator-api.nymtech.net/api" +NYXD="https://qa-validator.nymtech.net" +NYM_API="https://qa-validator-api.nymtech.net/api" + +DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600" \ No newline at end of file diff --git a/examples/cli/commands/verify-signature/Cargo.lock b/examples/cli/commands/verify-signature/Cargo.lock deleted file mode 100644 index fb341ab657..0000000000 --- a/examples/cli/commands/verify-signature/Cargo.lock +++ /dev/null @@ -1,3204 +0,0 @@ -# This file is automatically @generated by Cargo. -# 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 = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "anyhow" -version = "1.0.62" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" - -[[package]] -name = "async-trait" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" -dependencies = [ - "autocfg 1.1.0", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "az" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" - -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "base64ct" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" - -[[package]] -name = "bip32" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "873faa4363bfc54c36a48321da034c92a0645a363eed34d948683ffc1706e37f" -dependencies = [ - "bs58", - "hmac", - "k256", - "once_cell", - "pbkdf2", - "rand_core 0.6.3", - "ripemd160", - "sha2", - "subtle", - "zeroize", -] - -[[package]] -name = "bip39" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e89470017230c38e52b82b3ee3f530db1856ba1d434e3a67a3456a8a8dec5f" -dependencies = [ - "bitcoin_hashes", - "rand 0.6.5", - "rand_core 0.4.2", - "serde", - "unicode-normalization", -] - -[[package]] -name = "bitcoin_hashes" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - -[[package]] -name = "bls12_381" -version = "0.5.0" -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", - "rand_core 0.6.3", - "subtle", -] - -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" -dependencies = [ - "sha2", -] - -[[package]] -name = "bumpalo" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" - -[[package]] -name = "bytemuck" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "3.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "clap_lex", - "indexmap", - "once_cell", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_derive" -version = "3.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - -[[package]] -name = "coconut-bandwidth-contract-common" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "cosmwasm-std", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-interface" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "bs58", - "getset", - "nymcoconut", - "serde", - "thiserror", -] - -[[package]] -name = "colored" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" -dependencies = [ - "atty", - "lazy_static", - "winapi", -] - -[[package]] -name = "comfy-table" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85914173c2f558d61613bfbbf1911f14e630895087a7ed2fafc0f5319e1536e7" -dependencies = [ - "crossterm", - "strum", - "strum_macros", - "unicode-width", -] - -[[package]] -name = "config" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "cfg-if", - "handlebars", - "humantime-serde", - "log", - "network-defaults", - "serde", - "toml", - "url", -] - -[[package]] -name = "const-oid" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" - -[[package]] -name = "contracts-common" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "cosmwasm-std", -] - -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "cosmos-sdk-proto" -version = "0.12.3" -source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d" -dependencies = [ - "prost", - "prost-types", - "tendermint-proto", -] - -[[package]] -name = "cosmrs" -version = "0.7.1" -source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d" -dependencies = [ - "bip32", - "cosmos-sdk-proto", - "ecdsa", - "eyre", - "getrandom 0.2.7", - "k256", - "prost", - "prost-types", - "rand_core 0.6.3", - "serde", - "serde_json", - "subtle-encoding", - "tendermint", - "tendermint-rpc", - "thiserror", -] - -[[package]] -name = "cosmwasm-crypto" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" -dependencies = [ - "digest 0.9.0", - "ed25519-zebra", - "k256", - "rand_core 0.6.3", - "thiserror", -] - -[[package]] -name = "cosmwasm-derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" -dependencies = [ - "syn", -] - -[[package]] -name = "cosmwasm-std" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" -dependencies = [ - "base64", - "cosmwasm-crypto", - "cosmwasm-derive", - "forward_ref", - "schemars", - "serde", - "serde-json-wasm", - "thiserror", - "uint", -] - -[[package]] -name = "cpufeatures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossterm" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" -dependencies = [ - "bitflags", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" -dependencies = [ - "winapi", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-bigint" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" -dependencies = [ - "generic-array", - "rand_core 0.6.3", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array", - "subtle", -] - -[[package]] -name = "ct-logs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" -dependencies = [ - "sct", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", -] - -[[package]] -name = "cw-storage-plus" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-utils" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw3" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "schemars", - "serde", -] - -[[package]] -name = "cw4" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "schemars", - "serde", -] - -[[package]] -name = "der" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" -dependencies = [ - "const-oid", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "digest" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" -dependencies = [ - "block-buffer 0.10.2", - "crypto-common", -] - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "dyn-clone" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" - -[[package]] -name = "ecdsa" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" -dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", -] - -[[package]] -name = "ed25519" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" -dependencies = [ - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek", - "ed25519", - "sha2", - "zeroize", -] - -[[package]] -name = "ed25519-zebra" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" -dependencies = [ - "curve25519-dalek", - "hex", - "rand_core 0.6.3", - "serde", - "sha2", - "thiserror", - "zeroize", -] - -[[package]] -name = "either" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" - -[[package]] -name = "elliptic-curve" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" -dependencies = [ - "base16ct", - "crypto-bigint", - "der", - "ff 0.11.1", - "generic-array", - "group 0.11.0", - "rand_core 0.6.3", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "encoding_rs" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "example-verify-signature" -version = "0.1.0" -dependencies = [ - "nym-cli-commands", -] - -[[package]] -name = "execute" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "eyre" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] - -[[package]] -name = "ff" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" -dependencies = [ - "rand_core 0.6.3", - "subtle", -] - -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.3", - "subtle", -] - -[[package]] -name = "fixed" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8c4fbf8cd36f2a96740c31320902abbf0acbd733049b758707d842490c98c4" -dependencies = [ - "az", - "bytemuck", - "half", - "serde", - "typenum", -] - -[[package]] -name = "flate2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" -dependencies = [ - "crc32fast", - "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 = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "forward_ref" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "futures" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab30e97ab6aacfe635fad58f22c2bb06c8b685f7421eb1e064a729e2a5f481fa" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" - -[[package]] -name = "futures-executor" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d11aa21b5b587a64682c0094c2bdd4df0076c5324961a40cc3abd7f37930528" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a66fc6d035a26a3ae255a6d2bca35eda63ae4c5512bef54449113f7a1228e5" - -[[package]] -name = "futures-macro" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765" - -[[package]] -name = "futures-task" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306" - -[[package]] -name = "futures-util" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" -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", -] - -[[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.3", - "subtle", -] - -[[package]] -name = "group" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" -dependencies = [ - "ff 0.11.1", - "rand_core 0.6.3", - "subtle", -] - -[[package]] -name = "h2" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "half" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6a9459c9c30b177b925162351f97e7d967c7ea8bab3b8352805327daf45554" -dependencies = [ - "crunchy", -] - -[[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", - "serde", - "serde_json", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "headers" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" -dependencies = [ - "base64", - "bitflags", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha-1", -] - -[[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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hex" -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 = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac", - "digest 0.9.0", -] - -[[package]] -name = "http" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[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 = "httparse" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[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", - "serde", -] - -[[package]] -name = "hyper" -version = "0.14.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-proxy" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" -dependencies = [ - "bytes", - "futures", - "headers", - "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]] -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 = "idna" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de910d521f7cc3135c4de8db1cb910e0b5ed1dc6f57c381cd07e8e661ce10094" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" -dependencies = [ - "autocfg 1.1.0", - "hashbrown", - "serde", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" - -[[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" - -[[package]] -name = "js-sys" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "k256" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "sec1", - "sha2", - "sha3", -] - -[[package]] -name = "keccak" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.132" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" - -[[package]] -name = "lock_api" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" -dependencies = [ - "autocfg 1.1.0", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "miniz_oxide" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", -] - -[[package]] -name = "mixnet-contract-common" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "az", - "contracts-common", - "cosmwasm-std", - "fixed", - "log", - "schemars", - "serde", - "serde_repr", - "thiserror", - "time", - "ts-rs", -] - -[[package]] -name = "multisig-contract-common" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "cw3", - "cw4", - "schemars", - "serde", -] - -[[package]] -name = "native-tls" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "network-defaults" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "cfg-if", - "dotenv", - "hex-literal", - "once_cell", - "serde", - "thiserror", - "url", -] - -[[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", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg 1.1.0", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "nym-cli-commands" -version = "1.0.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "base64", - "bip39", - "bs58", - "cfg-if", - "clap", - "comfy-table", - "cosmrs", - "cosmwasm-std", - "handlebars", - "humantime-serde", - "k256", - "log", - "mixnet-contract-common", - "network-defaults", - "rand 0.6.5", - "serde", - "serde_json", - "thiserror", - "time", - "toml", - "url", - "validator-client", - "vesting-contract-common", -] - -[[package]] -name = "nymcoconut" -version = "0.5.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "bls12_381", - "bs58", - "digest 0.9.0", - "ff 0.10.1", - "getrandom 0.2.7", - "group 0.10.0", - "itertools", - "rand 0.8.5", - "serde", - "serde_derive", - "sha2", - "thiserror", -] - -[[package]] -name = "once_cell" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "openssl" -version = "0.10.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" -dependencies = [ - "autocfg 1.1.0", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "os_str_bytes" -version = "6.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" - -[[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 = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec 1.9.0", - "windows-sys", -] - -[[package]] -name = "paste" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" - -[[package]] -name = "pbkdf2" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" -dependencies = [ - "crypto-mac", -] - -[[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 = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pest" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0560d531d1febc25a3c9398a62a71256c0178f2e3443baedd9ad4bb8c9deb4" -dependencies = [ - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905708f7f674518498c1f8d644481440f476d39ca6ecae83319bba7c6c12da91" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5803d8284a629cc999094ecd630f55e91b561a1d1ba75e233b00ae13b91a69ad" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1538eb784f07615c6d9a8ab061089c6c54a344c5b4301db51990ca1c241e8c04" -dependencies = [ - "once_cell", - "pest", - "sha-1", -] - -[[package]] -name = "pin-project" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs8" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" -dependencies = [ - "der", - "spki", - "zeroize", -] - -[[package]] -name = "pkg-config" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-types" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" -dependencies = [ - "bytes", - "prost", -] - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.8", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom 0.2.7", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.4.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "reqwest" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "lazy_static", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - -[[package]] -name = "rfc6979" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" -dependencies = [ - "crypto-bigint", - "hmac", - "zeroize", -] - -[[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", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "ripemd160" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "rustls" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" -dependencies = [ - "base64", - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rustls-native-certs" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" -dependencies = [ - "openssl-probe", - "rustls", - "schannel", - "security-framework", -] - -[[package]] -name = "rustversion" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" - -[[package]] -name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" -dependencies = [ - "lazy_static", - "windows-sys", -] - -[[package]] -name = "schemars" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" -dependencies = [ - "dyn-clone", - "indexmap", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "sec1" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" -dependencies = [ - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.144" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-json-wasm" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_bytes" -version = "0.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.144" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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", -] - -[[package]] -name = "serde_json" -version = "1.0.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.3", -] - -[[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", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug", -] - -[[package]] -name = "signal-hook" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" -dependencies = [ - "digest 0.9.0", - "rand_core 0.6.3", -] - -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg 1.1.0", -] - -[[package]] -name = "smallvec" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] - -[[package]] -name = "smallvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" - -[[package]] -name = "socket2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spki" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[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 = "syn" -version = "1.0.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "tendermint" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca881fa4dedd2b46334f13be7fbc8cc1549ba4be5a833fe4e73d1a1baaf7949" -dependencies = [ - "async-trait", - "bytes", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures", - "k256", - "num-traits", - "once_cell", - "prost", - "prost-types", - "ripemd160", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto", - "time", - "zeroize", -] - -[[package]] -name = "tendermint-config" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c56ee93f4e9b7e7daba86d171f44572e91b741084384d0ae00df7991873dfd" -dependencies = [ - "flex-error", - "serde", - "serde_json", - "tendermint", - "toml", - "url", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71f925d74903f4abbdc4af0110635a307b3cb05b175fdff4a7247c14a4d0874" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost", - "prost-types", - "serde", - "serde_bytes", - "subtle-encoding", - "time", -] - -[[package]] -name = "tendermint-rpc" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13e63f57ee05a1e927887191c76d1b139de9fa40c180b9f8727ee44377242a6" -dependencies = [ - "async-trait", - "bytes", - "flex-error", - "futures", - "getrandom 0.2.7", - "http", - "hyper", - "hyper-proxy", - "hyper-rustls", - "peg", - "pin-project", - "serde", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", - "thiserror", - "time", - "tokio", - "tracing", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" - -[[package]] -name = "thiserror" -version = "1.0.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" -dependencies = [ - "itoa", - "libc", - "num_threads", - "time-macros", -] - -[[package]] -name = "time-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" - -[[package]] -name = "tokio" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" -dependencies = [ - "autocfg 1.1.0", - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "once_cell", - "pin-project-lite", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-macros" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -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", - "tokio", - "webpki", -] - -[[package]] -name = "tokio-util" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - -[[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.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "ts-rs" -version = "6.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc59f479df54269b400dd95bc3b7e81623b3e4b9c70c8ca7125ab8341eafa64e" -dependencies = [ - "thiserror", - "ts-rs-macros", -] - -[[package]] -name = "ts-rs-macros" -version = "6.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff" -dependencies = [ - "Inflector", - "proc-macro2", - "quote", - "syn", - "termcolor", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "ucd-trie" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" - -[[package]] -name = "uint" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - -[[package]] -name = "unicode-ident" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" - -[[package]] -name = "unicode-normalization" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf" -dependencies = [ - "smallvec 0.6.14", -] - -[[package]] -name = "unicode-width" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" - -[[package]] -name = "unicode-xid" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", - "serde", -] - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - -[[package]] -name = "validator-api-requests" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "bs58", - "coconut-interface", - "cosmrs", - "getset", - "mixnet-contract-common", - "schemars", - "serde", - "ts-rs", -] - -[[package]] -name = "validator-client" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "async-trait", - "base64", - "bip39", - "coconut-bandwidth-contract-common", - "coconut-interface", - "colored", - "config", - "cosmrs", - "cosmwasm-std", - "cw3", - "execute", - "flate2", - "futures", - "itertools", - "log", - "mixnet-contract-common", - "multisig-contract-common", - "network-defaults", - "prost", - "reqwest", - "serde", - "serde_json", - "sha2", - "thiserror", - "tokio", - "url", - "validator-api-requests", - "vesting-contract", - "vesting-contract-common", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "vesting-contract" -version = "1.0.2" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "mixnet-contract-common", - "schemars", - "serde", - "thiserror", - "vesting-contract-common", -] - -[[package]] -name = "vesting-contract-common" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym?branch=develop#124103d51b8fdd66552c27575234d810e80ec6c8" -dependencies = [ - "config", - "cosmwasm-std", - "cw-storage-plus", - "mixnet-contract-common", - "schemars", - "serde", - "ts-rs", -] - -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" - -[[package]] -name = "web-sys" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[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_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[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_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] -name = "zeroize" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] diff --git a/explorer-api/.env.sample b/explorer-api/.env.sample index 1efd2de597..e5922f9cf0 100644 --- a/explorer-api/.env.sample +++ b/explorer-api/.env.sample @@ -1,2 +1,2 @@ # The path to the geoip database file -GEOIP_DB_PATH=./geo_ip/GeoLite2-Country.mmdb +GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index cc665b1f83..a9dde6c94a 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,17 +1,17 @@ [package] name = "explorer-api" -version = "1.1.1" +version = "1.1.13" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] chrono = { version = "0.4.19", features = ["serde"] } -clap = { version = "3.2", features = ["cargo", "derive"] } +clap = { version = "4.0", features = ["cargo", "derive"] } humantime-serde = "1.0" isocountry = "0.3.2" itertools = "0.10.3" -log = "0.4.0" +log = { workspace = true } okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] } pretty_env_logger = "0.4.0" reqwest = "0.11.4" @@ -24,11 +24,14 @@ serde_json = "1.0.66" thiserror = "1.0.29" tokio = {version = "1.21.2", features = ["full"] } maxminddb = "0.23.0" -dotenv = "0.15.0" +dotenvy = "0.15.6" +rand = "0.8.5" +rand_seeder = "0.2.3" +rand_pcg = "0.3.1" -mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } -contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } -network-defaults = { path = "../common/network-defaults" } -logging = { path = "../common/logging"} -task = { path = "../common/task" } -validator-client = { path = "../common/client-libs/validator-client", features=["nymd-client"] } +nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } +nym-network-defaults = { path = "../common/network-defaults" } +nym-bin-common = { path = "../common/bin-common"} +nym-task = { path = "../common/task" } +validator-client = { path = "../common/client-libs/validator-client", features=["nyxd-client"] } diff --git a/explorer-api/README.md b/explorer-api/README.md index d4e193dbb0..86e64e4c12 100644 --- a/explorer-api/README.md +++ b/explorer-api/README.md @@ -40,7 +40,7 @@ It should be previously installed thanks to `geoipupdate` service. For example: ```shell -GEOIP_DB_PATH=./geo_ip/GeoLite2-Country.mmdb cargo run +GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb cargo run ``` Note: explorer-api binary reads the provided `.env` file. diff --git a/explorer-api/src/buy_terms/http.rs b/explorer-api/src/buy_terms/http.rs deleted file mode 100644 index 5594a1a3be..0000000000 --- a/explorer-api/src/buy_terms/http.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::geo_ip::location::Location; -use crate::state::ExplorerApiStateContext; -use rocket::response::status; -use rocket::serde::json::Json; -use rocket::{Route, State}; -use rocket_okapi::okapi::openapi3::OpenApi; -use rocket_okapi::settings::OpenApiSettings; - -pub fn nym_terms_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { - openapi_get_routes_spec![settings: terms] -} - -#[openapi(tag = "terms")] -#[get("/")] -pub(crate) async fn terms( - _state: &State, - location: Location, -) -> Result, status::Forbidden> { - if location.iso_alpha2 == "US" { - return Err(status::Forbidden(Some("US government sucks".to_string()))); - } - Ok(Json("Nym Terms & Conditions: Welcome".to_string())) -} diff --git a/explorer-api/src/buy_terms/mod.rs b/explorer-api/src/buy_terms/mod.rs deleted file mode 100644 index d064c8bd6f..0000000000 --- a/explorer-api/src/buy_terms/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod http; diff --git a/explorer-api/src/client.rs b/explorer-api/src/client.rs index 129a394d31..40e0e1d7f8 100644 --- a/explorer-api/src/client.rs +++ b/explorer-api/src/client.rs @@ -1,10 +1,10 @@ -use network_defaults::{ - var_names::{API_VALIDATOR, NYMD_VALIDATOR}, +use nym_network_defaults::{ + var_names::{NYM_API, NYXD}, NymNetworkDetails, }; use reqwest::Url; use std::{str::FromStr, sync::Arc}; -use validator_client::nymd::QueryNymdClient; +use validator_client::nyxd::QueryNyxdClient; // since this is just a query client, we don't need any locking mechanism to keep sequence numbers in check // nor we need to access any of its methods taking mutable reference (like updating api URL) @@ -12,7 +12,7 @@ use validator_client::nymd::QueryNymdClient; #[derive(Clone)] pub(crate) struct ThreadsafeValidatorClient( - pub(crate) Arc>, + pub(crate) Arc>, ); impl ThreadsafeValidatorClient { @@ -21,22 +21,22 @@ impl ThreadsafeValidatorClient { } pub(crate) fn api_endpoint(&self) -> &Url { - self.0.validator_api.current_url() + self.0.nym_api.current_url() } } pub(crate) fn new_validator_client() -> ThreadsafeValidatorClient { - let nymd_url = Url::from_str(&std::env::var(NYMD_VALIDATOR).expect("nymd validator not set")) - .expect("nymd validator not in url format"); - let api_url = Url::from_str(&std::env::var(API_VALIDATOR).expect("nymd validator not set")) - .expect("nymd validator not in url format"); + let nyxd_url = Url::from_str(&std::env::var(NYXD).expect("nyxd validator not set")) + .expect("nyxd validator not in url format"); + let api_url = Url::from_str(&std::env::var(NYM_API).expect("nyxd validator not set")) + .expect("nyxd validator not in url format"); let details = NymNetworkDetails::new_from_env(); let client_config = validator_client::Config::try_from_nym_network_details(&details) .expect("failed to construct valid validator client config with the provided network") - .with_urls(nymd_url, api_url); + .with_urls(nyxd_url, api_url); ThreadsafeValidatorClient(Arc::new( - validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"), + validator_client::Client::new_query(client_config).expect("Failed to connect to nyxd!"), )) } diff --git a/explorer-api/src/country_statistics/distribution.rs b/explorer-api/src/country_statistics/distribution.rs index f48e87eebb..f834b831c7 100644 --- a/explorer-api/src/country_statistics/distribution.rs +++ b/explorer-api/src/country_statistics/distribution.rs @@ -1,5 +1,5 @@ use log::info; -use task::ShutdownListener; +use nym_task::TaskClient; use crate::country_statistics::country_nodes_distribution::CountryNodesDistribution; use crate::COUNTRY_DATA_REFRESH_INTERVAL; @@ -8,11 +8,11 @@ use crate::state::ExplorerApiStateContext; pub(crate) struct CountryStatisticsDistributionTask { state: ExplorerApiStateContext, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl CountryStatisticsDistributionTask { - pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self { + pub(crate) fn new(state: ExplorerApiStateContext, shutdown: TaskClient) -> Self { CountryStatisticsDistributionTask { state, shutdown } } diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index c77a61a9a1..8158c6e65e 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -4,15 +4,15 @@ use crate::mix_nodes::location::Location; use crate::state::ExplorerApiStateContext; use log::{info, warn}; -use task::ShutdownListener; +use nym_task::TaskClient; pub(crate) struct GeoLocateTask { state: ExplorerApiStateContext, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl GeoLocateTask { - pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self { + pub(crate) fn new(state: ExplorerApiStateContext, shutdown: TaskClient) -> Self { GeoLocateTask { state, shutdown } } diff --git a/explorer-api/src/gateways/http.rs b/explorer-api/src/gateways/http.rs index 084507eed5..1f494d0edd 100644 --- a/explorer-api/src/gateways/http.rs +++ b/explorer-api/src/gateways/http.rs @@ -9,7 +9,7 @@ use rocket_okapi::openapi_get_routes_spec; use rocket_okapi::settings::OpenApiSettings; use crate::state::ExplorerApiStateContext; -use mixnet_contract_common::GatewayBond; +use nym_mixnet_contract_common::GatewayBond; pub fn gateways_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![settings: list] diff --git a/explorer-api/src/gateways/models.rs b/explorer-api/src/gateways/models.rs index a051d88e6b..ab2712c3fc 100644 --- a/explorer-api/src/gateways/models.rs +++ b/explorer-api/src/gateways/models.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::cache::Cache; -use mixnet_contract_common::{GatewayBond, IdentityKey}; +use nym_mixnet_contract_common::{GatewayBond, IdentityKey}; use serde::Serialize; use std::sync::Arc; use tokio::sync::RwLock; diff --git a/explorer-api/src/geo_ip/location.rs b/explorer-api/src/geo_ip/location.rs index a1afec55d6..adbdb6cef3 100644 --- a/explorer-api/src/geo_ip/location.rs +++ b/explorer-api/src/geo_ip/location.rs @@ -3,14 +3,14 @@ use isocountry::CountryCode; use log::warn; -use maxminddb::{geoip2::Country, MaxMindDBError, Reader}; +use maxminddb::{geoip2::City, MaxMindDBError, Reader}; use std::{ net::{IpAddr, ToSocketAddrs}, str::FromStr, sync::Arc, }; -const DEFAULT_DATABASE_PATH: &str = "./geo_ip/GeoLite2-Country.mmdb"; +const DEFAULT_DATABASE_PATH: &str = "./geo_ip/GeoLite2-City.mmdb"; const FAKE_PORT: u16 = 1234; #[derive(Debug)] @@ -38,6 +38,8 @@ pub(crate) struct Location { pub(crate) iso_alpha3: String, /// English country short name (ISO 3166-1) pub(crate) name: String, + pub(crate) latitude: Option, + pub(crate) longitude: Option, } impl GeoIp { @@ -86,7 +88,7 @@ impl GeoIp { error!("No registered GeoIP database"); GeoIpError::InternalError })? - .lookup::(ip); + .lookup::(ip); match &result { Ok(v) => Ok(Some( Location::try_from(v).map_err(|_| GeoIpError::InternalError)?, @@ -99,11 +101,11 @@ impl GeoIp { } } -impl<'a> TryFrom<&Country<'a>> for Location { +impl<'a> TryFrom<&City<'a>> for Location { type Error = String; - fn try_from(country: &Country) -> Result { - let data = country.country.as_ref().ok_or_else(|| { + fn try_from(city: &City) -> Result { + let data = city.country.as_ref().ok_or_else(|| { warn!("No Country data found"); "No Country data found" })?; @@ -119,10 +121,13 @@ impl<'a> TryFrom<&Country<'a>> for Location { warn!("{}", &message); message })?; + Ok(Location { iso_alpha2, iso_alpha3: String::from(iso_codes.alpha3()), name: String::from(iso_codes.name()), + latitude: city.location.as_ref().and_then(|l| l.latitude), + longitude: city.location.as_ref().and_then(|l| l.longitude), }) } } diff --git a/explorer-api/src/helpers.rs b/explorer-api/src/helpers.rs index 6b3d019833..cd271e838c 100644 --- a/explorer-api/src/helpers.rs +++ b/explorer-api/src/helpers.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use mixnet_contract_common::{Decimal, Fraction}; +use nym_mixnet_contract_common::{Decimal, Fraction}; pub(crate) fn best_effort_small_dec_to_f64(dec: Decimal) -> f64 { let num = dec.numerator().u128() as f64; diff --git a/explorer-api/src/http/mod.rs b/explorer-api/src/http/mod.rs index f5559124a5..559093671e 100644 --- a/explorer-api/src/http/mod.rs +++ b/explorer-api/src/http/mod.rs @@ -5,7 +5,6 @@ use rocket::{Build, Request, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins}; use rocket_okapi::swagger_ui::make_swagger_ui; -use crate::buy_terms::http::nym_terms_make_default_routes; use crate::country_statistics::http::country_statistics_make_default_routes; use crate::gateways::http::gateways_make_default_routes; use crate::http::swagger::get_docs; @@ -13,6 +12,7 @@ use crate::mix_node::http::mix_node_make_default_routes; use crate::mix_nodes::http::mix_nodes_make_default_routes; use crate::overview::http::overview_make_default_routes; use crate::ping::http::ping_make_default_routes; +use crate::service_providers::http::service_providers_make_default_routes; use crate::state::ExplorerApiStateContext; use crate::validators::http::validators_make_default_routes; @@ -57,7 +57,7 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket { "/overview" => overview_make_default_routes(&openapi_settings), "/ping" => ping_make_default_routes(&openapi_settings), "/validators" => validators_make_default_routes(&openapi_settings), - "/terms" => nym_terms_make_default_routes(&openapi_settings), + "/service-providers" => service_providers_make_default_routes(&openapi_settings), }; building_rocket diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index 47dddf9f54..676f1c5cea 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -4,13 +4,12 @@ extern crate rocket; extern crate rocket_okapi; use clap::Parser; -use dotenv::dotenv; +use dotenvy::dotenv; use log::info; -use logging::setup_logging; -use network_defaults::setup_env; -use task::ShutdownNotifier; +use nym_bin_common::logging::setup_logging; +use nym_network_defaults::setup_env; +use nym_task::TaskManager; -mod buy_terms; pub(crate) mod cache; mod client; pub(crate) mod commands; @@ -24,6 +23,7 @@ mod mix_node; pub(crate) mod mix_nodes; mod overview; mod ping; +pub(crate) mod service_providers; mod state; mod tasks; mod validators; @@ -35,7 +35,7 @@ async fn main() { dotenv().ok(); setup_logging(); let args = commands::Cli::parse(); - setup_env(args.config_env_file); + setup_env(args.config_env_file.as_ref()); let mut explorer_api = ExplorerApi::new(); explorer_api.run().await; } @@ -54,10 +54,10 @@ impl ExplorerApi { async fn run(&mut self) { info!("Explorer API starting up..."); - let validator_api_url = self.state.inner.validator_client.api_endpoint(); - info!("Using validator API - {}", validator_api_url); + let nym_api_url = self.state.inner.validator_client.api_endpoint(); + info!("Using validator API - {}", nym_api_url); - let shutdown = ShutdownNotifier::default(); + let shutdown = TaskManager::default(); // spawn concurrent tasks crate::tasks::ExplorerApiTasks::new(self.state.clone(), shutdown.subscribe()).start(); @@ -78,41 +78,8 @@ impl ExplorerApi { self.wait_for_interrupt(shutdown).await } - async fn wait_for_interrupt(&self, mut shutdown: ShutdownNotifier) { - wait_for_signal().await; - - log::info!("Sending shutdown"); - shutdown.signal_shutdown().ok(); - log::info!("Waiting for tasks to finish... (Press ctrl-c to force)"); - shutdown.wait_for_shutdown().await; + async fn wait_for_interrupt(&self, shutdown: TaskManager) { + let _res = shutdown.catch_interrupt().await; log::info!("Stopping explorer API"); } } - -#[cfg(unix)] -async fn wait_for_signal() { - use tokio::signal::unix::{signal, SignalKind}; - let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel"); - let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel"); - - tokio::select! { - _ = tokio::signal::ctrl_c() => { - log::info!("Received SIGINT"); - }, - _ = sigterm.recv() => { - log::info!("Received SIGTERM"); - } - _ = sigquit.recv() => { - log::info!("Received SIGQUIT"); - } - } -} - -#[cfg(not(unix))] -async fn wait_for_signal() { - tokio::select! { - _ = tokio::signal::ctrl_c() => { - log::info!("Received SIGINT"); - }, - } -} diff --git a/explorer-api/src/mix_node/delegations.rs b/explorer-api/src/mix_node/delegations.rs index 3d061c942a..d9c151214d 100644 --- a/explorer-api/src/mix_node/delegations.rs +++ b/explorer-api/src/mix_node/delegations.rs @@ -4,7 +4,7 @@ use super::models::SummedDelegations; use crate::client::ThreadsafeValidatorClient; use itertools::Itertools; -use mixnet_contract_common::{Delegation, MixId}; +use nym_mixnet_contract_common::{Delegation, MixId}; pub(crate) async fn get_single_mixnode_delegations( client: &ThreadsafeValidatorClient, @@ -12,7 +12,7 @@ pub(crate) async fn get_single_mixnode_delegations( ) -> Vec { match client .0 - .get_all_nymd_single_mixnode_delegations(mix_id) + .get_all_nyxd_single_mixnode_delegations(mix_id) .await { Ok(result) => result, diff --git a/explorer-api/src/mix_node/econ_stats.rs b/explorer-api/src/mix_node/econ_stats.rs index cde29d3a86..66a4676376 100644 --- a/explorer-api/src/mix_node/econ_stats.rs +++ b/explorer-api/src/mix_node/econ_stats.rs @@ -4,8 +4,8 @@ use crate::client::ThreadsafeValidatorClient; use crate::helpers::best_effort_small_dec_to_f64; use crate::mix_node::models::EconomicDynamicsStats; -use contracts_common::truncate_decimal; -use mixnet_contract_common::MixId; +use nym_contracts_common::truncate_decimal; +use nym_mixnet_contract_common::MixId; pub(crate) async fn retrieve_mixnode_econ_stats( client: &ThreadsafeValidatorClient, @@ -13,31 +13,26 @@ pub(crate) async fn retrieve_mixnode_econ_stats( ) -> Option { let stake_saturation = client .0 - .validator_api + .nym_api .get_mixnode_stake_saturation(mix_id) .await .ok()?; let inclusion_probability = client .0 - .validator_api + .nym_api .get_mixnode_inclusion_probability(mix_id) .await .ok()?; let reward_estimation = client .0 - .validator_api + .nym_api .get_mixnode_reward_estimation(mix_id) .await .ok()?; - let uptime_response = client - .0 - .validator_api - .get_mixnode_avg_uptime(mix_id) - .await - .ok()?; + let uptime_response = client.0.nym_api.get_mixnode_avg_uptime(mix_id).await.ok()?; Some(EconomicDynamicsStats { stake_saturation: best_effort_small_dec_to_f64(stake_saturation.saturation) as f32, diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index 00d9b9fd70..137c6250ed 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -9,7 +9,7 @@ use crate::mix_node::models::{ EconomicDynamicsStats, NodeDescription, NodeStats, PrettyDetailedMixNodeBond, SummedDelegations, }; use crate::state::ExplorerApiStateContext; -use mixnet_contract_common::{Delegation, MixId}; +use nym_mixnet_contract_common::{Delegation, MixId}; use reqwest::Error as ReqwestError; use rocket::response::status::NotFound; use rocket::serde::json::Json; @@ -30,14 +30,14 @@ pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec, } async fn get_mix_node_description(host: &str, port: u16) -> Result { - reqwest::get(format!("http://{}:{}/description", host, port)) + reqwest::get(format!("http://{host}:{port}/description")) .await? .json::() .await } async fn get_mix_node_stats(host: &str, port: u16) -> Result { - reqwest::get(format!("http://{}:{}/stats", host, port)) + reqwest::get(format!("http://{host}:{port}/stats")) .await? .json::() .await diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs index ef894e4b40..1a8d34dc7a 100644 --- a/explorer-api/src/mix_node/models.rs +++ b/explorer-api/src/mix_node/models.rs @@ -3,15 +3,15 @@ use crate::cache::Cache; use crate::mix_nodes::location::Location; -use contracts_common::Percent; -use mixnet_contract_common::Delegation; -use mixnet_contract_common::{Addr, Coin, Layer, MixId, MixNode}; +use nym_contracts_common::Percent; +use nym_mixnet_contract_common::Delegation; +use nym_mixnet_contract_common::{Addr, Coin, Layer, MixId, MixNode}; use serde::Deserialize; use serde::Serialize; use std::sync::Arc; use std::time::SystemTime; use tokio::sync::RwLock; -use validator_client::models::SelectionChance; +use validator_client::models::{NodePerformance, SelectionChance}; #[derive(Clone, Debug, Serialize, JsonSchema, PartialEq)] #[serde(rename_all = "snake_case")] @@ -36,10 +36,13 @@ pub(crate) struct PrettyDetailedMixNodeBond { pub stake_saturation: f32, pub uncapped_saturation: f32, pub avg_uptime: u8, + pub node_performance: NodePerformance, pub estimated_operator_apy: f64, pub estimated_delegators_apy: f64, pub operating_cost: Coin, pub profit_margin_percent: Percent, + pub family_id: Option, + pub blacklisted: bool, } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] diff --git a/explorer-api/src/mix_nodes/location.rs b/explorer-api/src/mix_nodes/location.rs index d14eaf771e..b876cdc792 100644 --- a/explorer-api/src/mix_nodes/location.rs +++ b/explorer-api/src/mix_nodes/location.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::geo_ip::location; -use mixnet_contract_common::MixId; +use nym_mixnet_contract_common::MixId; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::time::{Duration, SystemTime}; @@ -36,6 +36,8 @@ pub(crate) struct Location { pub(crate) two_letter_iso_country_code: String, pub(crate) three_letter_iso_country_code: String, pub(crate) country_name: String, + pub(crate) latitude: Option, + pub(crate) longitude: Option, } impl Location { @@ -44,6 +46,8 @@ impl Location { country_name: location.name, two_letter_iso_country_code: location.iso_alpha2, three_letter_iso_country_code: location.iso_alpha3, + latitude: location.latitude, + longitude: location.longitude, } } } diff --git a/explorer-api/src/mix_nodes/mod.rs b/explorer-api/src/mix_nodes/mod.rs index 82c2aedd1f..e2aced241a 100644 --- a/explorer-api/src/mix_nodes/mod.rs +++ b/explorer-api/src/mix_nodes/mod.rs @@ -6,6 +6,7 @@ use std::time::Duration; pub(crate) mod http; pub(crate) mod location; pub(crate) mod models; +pub(crate) mod utils; pub(crate) const CACHE_REFRESH_RATE: Duration = Duration::from_secs(30); pub(crate) const CACHE_ENTRY_TTL: Duration = Duration::from_secs(60); diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs index 8dc9c96ac3..0fc351ed89 100644 --- a/explorer-api/src/mix_nodes/models.rs +++ b/explorer-api/src/mix_nodes/models.rs @@ -5,14 +5,15 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; use std::time::{Duration, SystemTime}; -use mixnet_contract_common::rewarding::helpers::truncate_reward; -use mixnet_contract_common::MixId; +use nym_mixnet_contract_common::rewarding::helpers::truncate_reward; +use nym_mixnet_contract_common::MixId; use serde::Serialize; use tokio::sync::{RwLock, RwLockReadGuard}; use crate::helpers::best_effort_small_dec_to_f64; use validator_client::models::MixNodeBondAnnotated; +use super::utils::family_numerical_id; use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond}; use crate::mix_nodes::location::{Location, LocationCache, LocationCacheItem}; use crate::mix_nodes::CACHE_ENTRY_TTL; @@ -140,6 +141,8 @@ impl ThreadsafeMixNodesCache { let denom = &node.mixnode_details.original_pledge().denom; let rewarding_info = &node.mixnode_details.rewarding_details; + let family_id = node.family.as_ref().map(family_numerical_id); + PrettyDetailedMixNodeBond { mix_id, location: location.and_then(|l| l.location.clone()), @@ -150,6 +153,7 @@ impl ThreadsafeMixNodesCache { layer: node.mixnode_details.bond_information.layer, mix_node: node.mixnode_details.bond_information.mix_node.clone(), avg_uptime: node.performance.round_to_integer(), + node_performance: node.node_performance.clone(), stake_saturation: best_effort_small_dec_to_f64(node.stake_saturation) as f32, uncapped_saturation: best_effort_small_dec_to_f64(node.uncapped_stake_saturation) as f32, @@ -157,6 +161,8 @@ impl ThreadsafeMixNodesCache { estimated_delegators_apy: best_effort_small_dec_to_f64(node.estimated_delegators_apy), operating_cost: rewarding_info.cost_params.interval_operating_cost.clone(), profit_margin_percent: rewarding_info.cost_params.profit_margin_percent, + family_id, + blacklisted: node.blacklisted, } } diff --git a/explorer-api/src/mix_nodes/utils.rs b/explorer-api/src/mix_nodes/utils.rs index a151ec318a..0f7eb6b991 100644 --- a/explorer-api/src/mix_nodes/utils.rs +++ b/explorer-api/src/mix_nodes/utils.rs @@ -1,9 +1,15 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_mixnet_contract_common::families::FamilyHead; +use rand::Rng; +use rand_pcg::Pcg64; +use rand_seeder::Seeder; + use crate::mix_nodes::location::GeoLocation; use isocountry::CountryCode; +#[allow(dead_code)] pub(crate) fn map_2_letter_to_3_letter_country_code(geo: &GeoLocation) -> String { match CountryCode::for_alpha2(&geo.country_code) { Ok(three_letter_country_code) => three_letter_country_code.alpha3().to_string(), @@ -16,3 +22,10 @@ pub(crate) fn map_2_letter_to_3_letter_country_code(geo: &GeoLocation) -> String } } } + +// We don't need numerical IDs anywhere, so to avoid modifying the contract storage again and +// since this is for explorer ergonomics, it will generate a deterministic random u16 based on the family Identity. +pub(crate) fn family_numerical_id(fh: &FamilyHead) -> u16 { + let mut rng: Pcg64 = Seeder::from(fh.identity()).make_rng(); + rng.gen() +} diff --git a/explorer-api/src/ping/http.rs b/explorer-api/src/ping/http.rs index 7e7ebea06d..4e12e0f085 100644 --- a/explorer-api/src/ping/http.rs +++ b/explorer-api/src/ping/http.rs @@ -1,7 +1,7 @@ // Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use mixnet_contract_common::{MixId, MixNode}; +use nym_mixnet_contract_common::{MixId, MixNode}; use rocket::serde::json::Json; use rocket::{Route, State}; use rocket_okapi::okapi::openapi3::OpenApi; @@ -95,7 +95,7 @@ fn sanitize_and_resolve_host(host: &str, port: u16) -> Option { } // the host string should hopefully parse and resolve into a valid socket address - let parsed_host = format!("{}:{}", trimmed_host, port); + let parsed_host = format!("{trimmed_host}:{port}"); match parsed_host.to_socket_addrs() { Ok(mut addrs) => addrs.next(), Err(e) => { diff --git a/explorer-api/src/ping/models.rs b/explorer-api/src/ping/models.rs index 9463e12bb6..9aebf7d5fd 100644 --- a/explorer-api/src/ping/models.rs +++ b/explorer-api/src/ping/models.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::sync::Arc; use std::time::{Duration, SystemTime}; -use mixnet_contract_common::MixId; +use nym_mixnet_contract_common::MixId; use serde::Deserialize; use serde::Serialize; use tokio::sync::RwLock; diff --git a/explorer-api/src/service_providers/http.rs b/explorer-api/src/service_providers/http.rs new file mode 100644 index 0000000000..418e113a96 --- /dev/null +++ b/explorer-api/src/service_providers/http.rs @@ -0,0 +1,26 @@ +use crate::service_providers::models::DirectoryService; +use okapi::openapi3::OpenApi; +use reqwest::Error as ReqwestError; +use rocket::{serde::json::Json, Route}; +use rocket_okapi::settings::OpenApiSettings; + +static SERVICE_PROVIDER_WELLKNOWN_URL: &str = + "https://nymtech.net/.wellknown/connect/service-providers.json"; + +pub fn service_providers_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { + openapi_get_routes_spec![settings: get_service_providers] +} + +pub async fn get_services() -> Result, ReqwestError> { + reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL) + .await? + .json::>() + .await +} + +#[openapi(tag = "service_providers")] +#[get("/")] +pub(crate) async fn get_service_providers() -> Json> { + let result = get_services().await.unwrap(); + Json(result) +} diff --git a/explorer-api/src/service_providers/mod.rs b/explorer-api/src/service_providers/mod.rs new file mode 100644 index 0000000000..5df938f83c --- /dev/null +++ b/explorer-api/src/service_providers/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod http; +pub(crate) mod models; diff --git a/explorer-api/src/service_providers/models.rs b/explorer-api/src/service_providers/models.rs new file mode 100644 index 0000000000..21ee1df812 --- /dev/null +++ b/explorer-api/src/service_providers/models.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct DirectoryServiceProvider { + pub id: String, + pub description: String, + pub address: String, + pub gateway: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct DirectoryService { + pub id: String, + pub description: String, + pub items: Vec, +} diff --git a/explorer-api/src/state.rs b/explorer-api/src/state.rs index a511d5c0b8..ff1f0b3d34 100644 --- a/explorer-api/src/state.rs +++ b/explorer-api/src/state.rs @@ -3,7 +3,7 @@ use std::path::Path; use chrono::{DateTime, Utc}; use log::info; -use mixnet_contract_common::MixId; +use nym_mixnet_contract_common::MixId; use serde::{Deserialize, Serialize}; use crate::client::ThreadsafeValidatorClient; diff --git a/explorer-api/src/tasks.rs b/explorer-api/src/tasks.rs index 08d43fb805..2019d74dce 100644 --- a/explorer-api/src/tasks.rs +++ b/explorer-api/src/tasks.rs @@ -3,11 +3,11 @@ use std::future::Future; -use mixnet_contract_common::GatewayBond; -use task::ShutdownListener; +use nym_mixnet_contract_common::GatewayBond; +use nym_task::TaskClient; use validator_client::models::MixNodeBondAnnotated; -use validator_client::nymd::error::NymdError; -use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse}; +use validator_client::nyxd::error::NyxdError; +use validator_client::nyxd::{Paging, QueryNyxdClient, ValidatorResponse}; use validator_client::ValidatorClientError; use crate::mix_nodes::CACHE_REFRESH_RATE; @@ -15,24 +15,24 @@ use crate::state::ExplorerApiStateContext; pub(crate) struct ExplorerApiTasks { state: ExplorerApiStateContext, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl ExplorerApiTasks { - pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self { + pub(crate) fn new(state: ExplorerApiStateContext, shutdown: TaskClient) -> Self { ExplorerApiTasks { state, shutdown } } // a helper to remove duplicate code when grabbing active/rewarded/all mixnodes async fn retrieve_mixnodes<'a, F, Fut>(&'a self, f: F) -> Vec where - F: FnOnce(&'a validator_client::Client) -> Fut, + F: FnOnce(&'a validator_client::Client) -> Fut, Fut: Future, ValidatorClientError>>, { let bonds = match f(&self.state.inner.validator_client.0).await { Ok(result) => result, - Err(e) => { - error!("Unable to retrieve mixnode bonds: {:?}", e); + Err(err) => { + error!("Unable to retrieve mixnode bonds: {err}"); vec![] } }; @@ -43,7 +43,7 @@ impl ExplorerApiTasks { async fn retrieve_all_mixnodes(&self) -> Vec { info!("About to retrieve all mixnode bonds..."); - self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed) + self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed_unfiltered) .await } @@ -57,14 +57,14 @@ impl ExplorerApiTasks { .await } - async fn retrieve_all_validators(&self) -> Result { + async fn retrieve_all_validators(&self) -> Result { info!("About to retrieve all validators..."); let height = self .state .inner .validator_client .0 - .nymd + .nyxd .get_current_block_height() .await?; let response: ValidatorResponse = self @@ -72,7 +72,7 @@ impl ExplorerApiTasks { .inner .validator_client .0 - .nymd + .nyxd .get_validators(height.value(), Paging::All) .await?; info!("Fetched {} validators", response.validators.len()); @@ -115,8 +115,8 @@ impl ExplorerApiTasks { async fn update_validators_cache(&self) { match self.retrieve_all_validators().await { Ok(response) => self.state.inner.validators.update_cache(response).await, - Err(e) => { - error!("Failed to get validators: {:?}", e) + Err(err) => { + error!("Failed to get validators: {err}") } } } @@ -124,8 +124,8 @@ impl ExplorerApiTasks { async fn update_gateways_cache(&self) { match self.retrieve_all_gateways().await { Ok(response) => self.state.inner.gateways.update_cache(response).await, - Err(e) => { - error!("Failed to get gateways: {:?}", e) + Err(err) => { + error!("Failed to get gateways: {err}") } } } diff --git a/explorer-api/src/validators/models.rs b/explorer-api/src/validators/models.rs index cc68b58f40..a66052fbbb 100644 --- a/explorer-api/src/validators/models.rs +++ b/explorer-api/src/validators/models.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use serde::Serialize; use tokio::sync::RwLock; -use validator_client::nymd::ValidatorResponse; +use validator_client::nyxd::ValidatorResponse; use crate::cache::Cache; diff --git a/explorer/.env.prod b/explorer/.env.prod index b1b1fe0d4c..70bd50c7ca 100644 --- a/explorer/.env.prod +++ b/explorer/.env.prod @@ -1,5 +1,5 @@ EXPLORER_API_URL=https://explorer.nymtech.net/api/v1 -VALIDATOR_API_URL=https://validator.nymtech.net +NYM_API_URL=https://validator.nymtech.net VALIDATOR_URL=https://rpc.nymtech.net BIG_DIPPER_URL=https://blocks.nymtech.net CURRENCY_DENOM=unym diff --git a/explorer/.env.qa b/explorer/.env.qa index 60b30e1bd1..dd7052ce4d 100644 --- a/explorer/.env.qa +++ b/explorer/.env.qa @@ -1,5 +1,5 @@ EXPLORER_API_URL=https://qa-explorer.nymtech.net/api/v1 -VALIDATOR_API_URL=https://qa-validator-api.nymtech.net +NYM_API_URL=https://qa-validator-api.nymtech.net VALIDATOR_URL=https://qa-validator.nymtech.net BIG_DIPPER_URL=https://qa-blocks.nymtech.net CURRENCY_DENOM=unym diff --git a/explorer/CHANGELOG.md b/explorer/CHANGELOG.md new file mode 100644 index 0000000000..8446a3c5b2 --- /dev/null +++ b/explorer/CHANGELOG.md @@ -0,0 +1,47 @@ +## UNRELEASED + +- nothing yet + +## [nym-explorer-v1.0.6](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.6) (2023-02-21) + +- Allow user to filter gateways but current and old versions +- Add copy buttons to gateway identity keys + +## [nym-explorer-v1.0.5](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.5) (2023-02-14) + +- NE - link `Owner` field on the node detail page to the account details on NG explorer ([#2923]) +- NE - Upgrade Sandbox and make below changes: ([#2332]) + +[#2923]: https://github.com/nymtech/nym/issues/2923 +[#2332]: https://github.com/nymtech/nym/issues/2332 + +## [nym-explorer-v1.0.4](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.4) (2023-01-31) + +- Add routing score on gateway list ([#2913]) +- Add gateway's last Routing Score to the gateways list page ([#2186]) +- Upgrade Sandbox and make below changes: ([#2332]) + +[#2913]: https://github.com/nymtech/nym/pull/2913 +[#2186]: https://github.com/nymtech/nym/issues/2186 +[#2332]: https://github.com/nymtech/nym/issues/2332 + +## [nym-explorer-v1.0.3](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.3) (2023-01-24) + +- Stake Saturation tooltip on node list and node pages updated ([#2877]) +- Sandbox Upgrade : Name change from "Network Explorer" to "Sandbox Explorer", button to mainnet explorer and link to faucet ([#2332)]) + +[#2877]: https://github.com/nymtech/nym/issues/2877 +[#2332]: https://github.com/nymtech/nym/issues/2332 + +## [nym-explorer-v1.0.2](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.2) (2023-01-17) + +- changing the explorers guru link ([#2820]) + +[#2820]: https://github.com/nymtech/nym/pull/2820 + +## [nym-explorer-v1.0.1](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.1) (2023-01-10) + +- Feat/2161 ne gate version by @gala1234 in https://github.com/nymtech/nym/pull/2743 +- fix(explorer): set gateway bond 6 decimals by @doums in https://github.com/nymtech/nym/pull/2741 +- Feat/2130 tables update rebase by @gala1234 in https://github.com/nymtech/nym/pull/2742 +- fix(explorer,explorer-api): mixnode location by @doums in https://github.com/nymtech/nym-api diff --git a/explorer/package.json b/explorer/package.json index b314878308..9290a8c750 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -1,6 +1,6 @@ { "name": "@nym/network-explorer", - "version": "1.0.0", + "version": "1.0.7", "private": true, "license": "Apache-2.0", "dependencies": { @@ -15,19 +15,20 @@ "@nymproject/mui-theme": "^1.0.0", "@nymproject/nym-validator-client": "^0.18.0", "@nymproject/react": "^1.0.0", + "big.js": "^6.2.1", "d3-scale": "^4.0.0", "date-fns": "^2.24.0", "i18n-iso-countries": "^6.8.0", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", "react-google-charts": "^3.0.15", "react-identicons": "^1.2.5", "react-router-dom": "6", "react-simple-maps": "^2.3.0", "react-tooltip": "^4.2.21", - "use-clipboard-copy": "^0.2.0", - "big.js": "^6.2.1" + "semver": "^7.3.8", + "use-clipboard-copy": "^0.2.0" }, "devDependencies": { "@babel/core": "^7.15.0", @@ -37,7 +38,7 @@ "@storybook/addon-essentials": "^6.5.8", "@storybook/addon-interactions": "^6.5.8", "@storybook/addon-links": "^6.5.8", - "@storybook/react": "^6.5.8", + "@storybook/react": "^6.5.15", "@storybook/testing-library": "^0.0.9", "@svgr/webpack": "^6.1.1", "@testing-library/jest-dom": "^5.14.1", @@ -48,17 +49,17 @@ "@types/geojson": "^7946.0.8", "@types/jest": "^27.0.1", "@types/node": "^16.7.13", - "@types/react": "^17.0.34", - "@types/react-dom": "^17.0.9", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.10", "@types/react-simple-maps": "^1.0.6", "@types/react-tooltip": "^4.2.4", "@types/topojson-client": "^3.1.0", "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.13.0", - "babel-loader": "^8.2.2", + "babel-loader": "^8.3.0", "babel-plugin-root-import": "^6.6.0", "clean-webpack-plugin": "^4.0.0", - "css-loader": "^6.2.0", + "css-loader": "^6.7.3", "css-minimizer-webpack-plugin": "^3.0.2", "dotenv-webpack": "^7.0.3", "eslint": "^8.10.0", @@ -82,13 +83,13 @@ "prettier": "2.3.2", "react-refresh": "^0.10.0", "react-refresh-typescript": "^2.0.2", - "style-loader": "^3.2.1", + "style-loader": "^3.3.1", "ts-jest": "^27.0.5", - "ts-loader": "^9.2.5", + "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^3.5.2", "typescript": "^4.6.2", "url-loader": "^4.1.1", - "webpack": "^5.64.3", + "webpack": "^5.75.0", "webpack-cli": "^4.8.0", "webpack-dev-server": "^4.5.0", "webpack-favicons": "^1.3.8", @@ -120,4 +121,4 @@ "last 1 safari version" ] } -} +} \ No newline at end of file diff --git a/explorer/src/App.tsx b/explorer/src/App.tsx index 9ce60f06f9..4e6fcc41e8 100644 --- a/explorer/src/App.tsx +++ b/explorer/src/App.tsx @@ -4,7 +4,7 @@ import { MobileNav } from './components/MobileNav'; import { Routes } from './routes/index'; import { useIsMobile } from './hooks/useIsMobile'; -export const App: React.FC = () => { +export const App: FCWithChildren = () => { const isMobile = useIsMobile(); if (isMobile) { diff --git a/explorer/src/api/constants.ts b/explorer/src/api/constants.ts index fb7216b64f..e3f39a3cae 100644 --- a/explorer/src/api/constants.ts +++ b/explorer/src/api/constants.ts @@ -1,6 +1,6 @@ // master APIs export const API_BASE_URL = process.env.EXPLORER_API_URL; -export const VALIDATOR_API_BASE_URL = process.env.VALIDATOR_API_URL; +export const NYM_API_BASE_URL = process.env.NYM_API_URL; export const VALIDATOR_BASE_URL = process.env.VALIDATOR_URL; export const BIG_DIPPER = process.env.BIG_DIPPER_URL; @@ -9,14 +9,17 @@ export const OVERVIEW_API = `${API_BASE_URL}/overview`; export const MIXNODE_PING = `${API_BASE_URL}/ping`; export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`; export const MIXNODE_API = `${API_BASE_URL}/mix-node`; -export const GATEWAYS_API = `${VALIDATOR_API_BASE_URL}/api/v1/gateways`; +export const GATEWAYS_API = `${NYM_API_BASE_URL}/api/v1/status/gateways/detailed`; export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`; -export const BLOCK_API = `${VALIDATOR_API_BASE_URL}/block`; +export const BLOCK_API = `${NYM_API_BASE_URL}/block`; export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`; -export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this. -export const UPTIME_STORY_API_GATEWAY = `${VALIDATOR_API_BASE_URL}/api/v1/status/gateway`; // add ID then '/history' or '/report' to this. +export const UPTIME_STORY_API = `${NYM_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this. +export const UPTIME_STORY_API_GATEWAY = `${NYM_API_BASE_URL}/api/v1/status/gateway`; // add ID then '/history' or '/report' to this +export const SERVICE_PROVIDERS = `${API_BASE_URL}/service-providers`; // errors export const MIXNODE_API_ERROR = "We're having trouble finding that record, please try again or Contact Us."; export const NYM_WEBSITE = 'https://nymtech.net'; + +export const NYM_BIG_DIPPER = 'https://mixnet.explorers.guru'; diff --git a/explorer/src/api/index.ts b/explorer/src/api/index.ts index 37e68b8096..fa3dae5cb1 100644 --- a/explorer/src/api/index.ts +++ b/explorer/src/api/index.ts @@ -1,4 +1,5 @@ import { + API_BASE_URL, BLOCK_API, COUNTRY_DATA_API, GATEWAYS_API, @@ -9,13 +10,13 @@ import { OVERVIEW_API, UPTIME_STORY_API, VALIDATORS_API, + SERVICE_PROVIDERS, } from './constants'; import { CountryDataResponse, DelegationsResponse, UniqDelegationsResponse, - GatewayResponse, GatewayReportResponse, UptimeStoryResponse, MixNodeDescriptionResponse, @@ -27,6 +28,10 @@ import { StatusResponse, SummaryOverviewResponse, ValidatorsResponse, + Environment, + GatewayBondAnnotated, + GatewayBond, + DirectoryService, } from '../typeDefs/explorer-api'; function getFromCache(key: string) { @@ -61,6 +66,7 @@ export class Api { if (cachedMixnodes) { return cachedMixnodes; } + const res = await fetch(MIXNODES_API); const json = await res.json(); storeInCache('mixnodes', JSON.stringify(json)); @@ -89,9 +95,13 @@ export class Api { return response.json(); }; - static fetchGateways = async (): Promise => { + static fetchGateways = async (): Promise => { const res = await fetch(GATEWAYS_API); - return res.json(); + const gatewaysAnnotated: GatewayBondAnnotated[] = await res.json(); + return gatewaysAnnotated.map(({ gateway_bond, node_performance }) => ({ + ...gateway_bond, + node_performance, + })); }; static fetchGatewayUptimeStoryById = async (id: string): Promise => @@ -142,4 +152,15 @@ export class Api { static fetchUptimeStoryById = async (id: string): Promise => (await fetch(`${UPTIME_STORY_API}/${id}/history`)).json(); + + static fetchServiceProviders = async (): Promise => { + const res = await fetch(SERVICE_PROVIDERS); + const json = await res.json(); + return json; + }; } + +export const getEnvironment = (): Environment => { + const matchEnv = (env: Environment) => API_BASE_URL?.toLocaleLowerCase().includes(env) && env; + return matchEnv('sandbox') || matchEnv('qa') || 'mainnet'; +}; diff --git a/explorer/src/components/ComponentError.tsx b/explorer/src/components/ComponentError.tsx index 34ca4b9d19..00b448fb9e 100644 --- a/explorer/src/components/ComponentError.tsx +++ b/explorer/src/components/ComponentError.tsx @@ -1,7 +1,7 @@ import { Typography } from '@mui/material'; import * as React from 'react'; -export const ComponentError: React.FC<{ text: string }> = ({ text }) => ( +export const ComponentError: FCWithChildren<{ text: string }> = ({ text }) => ( = ({ +export const ContentCard: FCWithChildren = ({ title, Icon, Action, diff --git a/explorer/src/components/CustomColumnHeading.tsx b/explorer/src/components/CustomColumnHeading.tsx index 4adcda4540..1f85806fa2 100644 --- a/explorer/src/components/CustomColumnHeading.tsx +++ b/explorer/src/components/CustomColumnHeading.tsx @@ -1,21 +1,16 @@ import * as React from 'react'; import { Box, Typography } from '@mui/material'; -import { ExpandLess, ExpandMore } from '@mui/icons-material'; import { useTheme } from '@mui/material/styles'; import { Tooltip } from '@nymproject/react/tooltip/Tooltip'; -export const CustomColumnHeading: React.FC<{ headingTitle: string; tooltipInfo?: string }> = ({ +export const CustomColumnHeading: FCWithChildren<{ headingTitle: string; tooltipInfo?: string }> = ({ headingTitle, tooltipInfo, }) => { - const [filter, toggleFilter] = React.useState(false); const theme = useTheme(); - const handleClick = () => { - toggleFilter(!filter); - }; return ( - + {tooltipInfo && ( )} - - {headingTitle}  + + {headingTitle} - {filter ? : } ); }; diff --git a/explorer/src/components/DetailTable.tsx b/explorer/src/components/DetailTable.tsx index 1aab097a05..d333f0eb59 100644 --- a/explorer/src/components/DetailTable.tsx +++ b/explorer/src/components/DetailTable.tsx @@ -1,20 +1,30 @@ import * as React from 'react'; -import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material'; +import { + Link, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TableCellProps, +} from '@mui/material'; import { useTheme } from '@mui/material/styles'; import { Tooltip } from '@nymproject/react/tooltip/Tooltip'; import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard'; import { Box } from '@mui/system'; import { cellStyles } from './Universal-DataGrid'; -import { currencyToString } from '../utils/currency'; +import { unymToNym } from '../utils/currency'; import { GatewayEnrichedRowType } from './Gateways'; import { MixnodeRowType } from './MixNodes'; +import { StakeSaturationProgressBar } from './MixNodes/Economics/StakeSaturationProgressBar'; export type ColumnsType = { field: string; title: string; - headerAlign: string; - flex?: number; - width?: number; + headerAlign?: TableCellProps['align']; + width?: string | number; tooltipInfo?: string; }; @@ -37,13 +47,27 @@ function formatCellValues(val: string | number, field: string) { ); } + if (field === 'bond') { - return currencyToString(val.toString()); + return unymToNym(val, 6); } + + if (field === 'owner') { + return ( + + {val} + + ); + } + + if (field === 'stake_saturation') { + return ; + } + return val; } -export const DetailTable: React.FC<{ +export const DetailTable: FCWithChildren<{ tableName: string; columnsData: ColumnsType[]; rows: MixnodeRowType[] | GatewayEnrichedRowType[]; @@ -51,11 +75,11 @@ export const DetailTable: React.FC<{ const theme = useTheme(); return ( - +
- {columnsData?.map(({ field, title, flex, tooltipInfo }) => ( - + {columnsData?.map(({ field, title, width, tooltipInfo }) => ( + {tooltipInfo && ( diff --git a/explorer/src/components/Footer.tsx b/explorer/src/components/Footer.tsx index 2ee7c77198..c85ba47be4 100644 --- a/explorer/src/components/Footer.tsx +++ b/explorer/src/components/Footer.tsx @@ -4,7 +4,7 @@ import { Typography } from '@mui/material'; import { Socials } from './Socials'; import { useIsMobile } from '../hooks/useIsMobile'; -export const Footer: React.FC = () => { +export const Footer: FCWithChildren = () => { const isMobile = useIsMobile(); return ( diff --git a/explorer/src/components/Gateways.ts b/explorer/src/components/Gateways.ts index e75c9aeb05..b78b1320da 100644 --- a/explorer/src/components/Gateways.ts +++ b/explorer/src/components/Gateways.ts @@ -1,12 +1,15 @@ -import { GatewayResponse, GatewayResponseItem, GatewayReportResponse } from '../typeDefs/explorer-api'; +import { GatewayResponse, GatewayBond, GatewayReportResponse } from '../typeDefs/explorer-api'; +import { toPercentIntegerString } from '../utils'; export type GatewayRowType = { id: string; owner: string; - identityKey: string; + identity_key: string; bond: number; host: string; location: string; + version: string; + node_performance: string; }; export type GatewayEnrichedRowType = GatewayRowType & { @@ -22,27 +25,28 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy : arrayOfGateways.map((gw) => ({ id: gw.owner, owner: gw.owner, - identityKey: gw.gateway.identity_key || '', + identity_key: gw.gateway.identity_key || '', location: gw?.gateway?.location || '', bond: gw.pledge_amount.amount || 0, host: gw.gateway.host || '', + version: gw.gateway.version || '', + node_performance: toPercentIntegerString(gw.node_performance.last_24h), })); } -export function gatewayEnrichedToGridRow( - gateway: GatewayResponseItem, - report: GatewayReportResponse, -): GatewayEnrichedRowType { +export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayReportResponse): GatewayEnrichedRowType { return { id: gateway.owner, owner: gateway.owner, - identityKey: gateway.gateway.identity_key || '', + identity_key: gateway.gateway.identity_key || '', location: gateway?.gateway?.location || '', bond: gateway.pledge_amount.amount || 0, host: gateway.gateway.host || '', + version: gateway.gateway.version || '', clientsPort: gateway.gateway.clients_port || 0, mixPort: gateway.gateway.mix_port || 0, routingScore: `${report.most_recent}%`, avgUptime: `${report.last_day || report.last_hour}%`, + node_performance: toPercentIntegerString(gateway.node_performance.most_recent), }; } diff --git a/explorer/src/components/Gateways/VersionDisplaySelector.tsx b/explorer/src/components/Gateways/VersionDisplaySelector.tsx new file mode 100644 index 0000000000..927e46c026 --- /dev/null +++ b/explorer/src/components/Gateways/VersionDisplaySelector.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { FormControl, MenuItem, Select } from '@mui/material'; +import { useIsMobile } from '../../hooks/useIsMobile'; + +export enum VersionSelectOptions { + latestVersion = 'Latest versions', + olderVersions = 'Older versions', +} +export const VersionDisplaySelector = ({ + selected, + handleChange, +}: { + selected: VersionSelectOptions; + handleChange: (option: VersionSelectOptions) => void; +}) => { + const isMobile = useIsMobile(); + + return ( + + + + ); +}; diff --git a/explorer/src/components/MixNodes/BondBreakdown.tsx b/explorer/src/components/MixNodes/BondBreakdown.tsx index 06009be05d..d7faa163fc 100644 --- a/explorer/src/components/MixNodes/BondBreakdown.tsx +++ b/explorer/src/components/MixNodes/BondBreakdown.tsx @@ -13,7 +13,7 @@ import { currencyToString } from '../../utils/currency'; import { useMixnodeContext } from '../../context/mixnode'; import { useIsMobile } from '../../hooks/useIsMobile'; -export const BondBreakdownTable: React.FC = () => { +export const BondBreakdownTable: FCWithChildren = () => { const { mixNode, delegations, uniqDelegations } = useMixnodeContext(); const [showDelegations, toggleShowDelegations] = React.useState(false); diff --git a/explorer/src/components/MixNodes/DetailSection.tsx b/explorer/src/components/MixNodes/DetailSection.tsx index b338487fd2..a07a6fb7d8 100644 --- a/explorer/src/components/MixNodes/DetailSection.tsx +++ b/explorer/src/components/MixNodes/DetailSection.tsx @@ -11,15 +11,15 @@ interface MixNodeDetailProps { mixnodeDescription: MixNodeDescriptionResponse; } -export const MixNodeDetailSection: React.FC = ({ mixNodeRow, mixnodeDescription }) => { +export const MixNodeDetailSection: FCWithChildren = ({ mixNodeRow, mixnodeDescription }) => { const theme = useTheme(); const palette = [theme.palette.text.primary]; const isMobile = useIsMobile(); const statusText = React.useMemo(() => getMixNodeStatusText(mixNodeRow.status), [mixNodeRow.status]); return ( - - + + = ({ mixNodeRow, > - + {mixnodeDescription.name} {(mixnodeDescription.description || '').slice(0, 1000)} - + = ({ isFooter }) => { +export const Socials: FCWithChildren<{ isFooter?: boolean }> = ({ isFooter }) => { const theme = useTheme(); const color = isFooter ? theme.palette.nym.networkExplorer.footer.socialIcons diff --git a/explorer/src/components/StatsCard.tsx b/explorer/src/components/StatsCard.tsx index a571441015..36355b1759 100644 --- a/explorer/src/components/StatsCard.tsx +++ b/explorer/src/components/StatsCard.tsx @@ -11,7 +11,14 @@ interface StatsCardProps { onClick?: () => void; color?: string; } -export const StatsCard: React.FC = ({ icon, title, count, onClick, errorMsg, color: colorProp }) => { +export const StatsCard: FCWithChildren = ({ + icon, + title, + count, + onClick, + errorMsg, + color: colorProp, +}) => { const theme = useTheme(); const color = colorProp || theme.palette.text.primary; return ( diff --git a/explorer/src/components/Switch.tsx b/explorer/src/components/Switch.tsx index 835100a1db..840530a327 100644 --- a/explorer/src/components/Switch.tsx +++ b/explorer/src/components/Switch.tsx @@ -51,16 +51,16 @@ export const DarkLightSwitch = styled(Switch)(({ theme }) => ({ }, })); -export const DarkLightSwitchMobile: React.FC = () => { +export const DarkLightSwitchMobile: FCWithChildren = () => { const { toggleMode } = useMainContext(); return ( - ); }; -export const DarkLightSwitchDesktop: React.FC<{ defaultChecked: boolean }> = ({ defaultChecked }) => { +export const DarkLightSwitchDesktop: FCWithChildren<{ defaultChecked: boolean }> = ({ defaultChecked }) => { const { toggleMode } = useMainContext(); return ( + + ), + }, + { + field: 'host', + renderHeader: () => , + width: 180, + disableColumnMenu: true, + headerAlign: 'left', + headerClassName: 'MuiDataGrid-header-override', + renderCell: (params: GridRenderCellParams) => ( + + {params.value} + ), }, { field: 'owner', headerName: 'Owner', renderHeader: () => , - width: 380, + width: 180, + disableColumnMenu: true, headerAlign: 'left', headerClassName: 'MuiDataGrid-header-override', renderCell: (params: GridRenderCellParams) => ( @@ -142,6 +229,25 @@ export const PageGateways: React.FC = () => { ), }, + { + field: 'bond', + width: 150, + disableColumnMenu: true, + type: 'number', + renderHeader: () => , + headerClassName: 'MuiDataGrid-header-override', + headerAlign: 'left', + renderCell: (params: GridRenderCellParams) => ( + + {unymToNym(params.value, 6)} + + ), + }, ]; const handlePageSize = (event: SelectChangeEvent) => { @@ -165,6 +271,12 @@ export const PageGateways: React.FC = () => { onChangePageSize={handlePageSize} pageSize={pageSize} searchTerm={searchTerm} + childrenBefore={ + setVersionFilter(option)} + selected={versionFilter} + /> + } /> { +const PageMixnodeDetailWithState: FCWithChildren = () => { const { mixNode, mixNodeRow, description, stats, status, uptimeStory, uniqDelegations } = useMixnodeContext(); - + const isMobile = useIsMobile(); return ( - <Grid container spacing={2} mt={1} mb={6}> <Grid item xs={12}> {mixNodeRow && description?.data && ( <MixNodeDetailSection mixNodeRow={mixNodeRow} mixnodeDescription={description.data} /> )} + {mixNodeRow?.blacklisted && ( + <Typography textAlign={isMobile ? 'left' : 'right'} fontSize="smaller" sx={{ color: 'error.main' }}> + This node is having a poor performance + </Typography> + )} </Grid> </Grid> - <Grid container> <Grid item xs={12}> <DetailTable columnsData={columns} tableName="Mixnode detail table" rows={mixNodeRow ? [mixNodeRow] : []} /> </Grid> </Grid> - <Grid container spacing={2} mt={0}> <Grid item xs={12}> <DelegatorsInfoTable @@ -92,7 +96,6 @@ const PageMixnodeDetailWithState: React.FC = () => { /> </Grid> </Grid> - <Grid container spacing={2} mt={0}> <Grid item xs={12}> <ContentCard title={`Stake Breakdown (${uniqDelegations?.data?.length} delegators)`}> @@ -100,7 +103,6 @@ const PageMixnodeDetailWithState: React.FC = () => { </ContentCard> </Grid> </Grid> - <Grid container spacing={2} mt={0}> <Grid item xs={12} md={4}> <ContentCard title="Mixnode Stats"> @@ -139,12 +141,16 @@ const PageMixnodeDetailWithState: React.FC = () => { {uptimeStory && ( <ContentCard title="Routing Score"> {uptimeStory.error && <ComponentError text="There was a problem retrieving routing score." />} - <UptimeChart loading={uptimeStory.isLoading} xLabel="date" uptimeStory={uptimeStory} /> + <UptimeChart + loading={uptimeStory.isLoading} + xLabel="Date" + yLabel="Daily average" + uptimeStory={uptimeStory} + /> </ContentCard> )} </Grid> </Grid> - <Grid container spacing={2} mt={0}> <Grid item xs={12} md={4}> {status && ( @@ -164,10 +170,10 @@ const PageMixnodeDetailWithState: React.FC = () => { {mixNode && ( <ContentCard title="Location"> {mixNode?.error && <ComponentError text="There was a problem retrieving this mixnode location" />} - {mixNode.data && mixNode?.data?.location && ( + {mixNode?.data?.location?.latitude && mixNode?.data?.location?.longitude && ( <WorldMap loading={mixNode.isLoading} - userLocation={[mixNode?.data?.location?.lng, mixNode?.data?.location?.lat]} + userLocation={[mixNode.data.location.longitude, mixNode.data.location.latitude]} /> )} </ContentCard> @@ -181,7 +187,7 @@ const PageMixnodeDetailWithState: React.FC = () => { /** * Guard component to handle loading and not found states */ -const PageMixnodeDetailGuard: React.FC = () => { +const PageMixnodeDetailGuard: FCWithChildren = () => { const { mixNode } = useMixnodeContext(); const { id } = useParams<{ id: string | undefined }>(); @@ -215,7 +221,7 @@ const PageMixnodeDetailGuard: React.FC = () => { /** * Wrapper component that adds the mixnode content based on the `id` in the address URL */ -export const PageMixnodeDetail: React.FC = () => { +export const PageMixnodeDetail: FCWithChildren = () => { const { id } = useParams<{ id: string | undefined }>(); if (!id) { diff --git a/explorer/src/pages/Mixnodes/index.tsx b/explorer/src/pages/Mixnodes/index.tsx index c502799c54..d42b99efef 100644 --- a/explorer/src/pages/Mixnodes/index.tsx +++ b/explorer/src/pages/Mixnodes/index.tsx @@ -10,7 +10,7 @@ import { useMainContext } from '../../context/main'; import { MixnodeRowType, mixnodeToGridRow } from '../../components/MixNodes'; import { TableToolbar } from '../../components/TableToolbar'; import { MixNodeResponse, MixnodeStatusWithAll, toMixnodeStatus } from '../../typeDefs/explorer-api'; -import { BIG_DIPPER } from '../../api/constants'; +import { NYM_BIG_DIPPER } from '../../api/constants'; import { CustomColumnHeading } from '../../components/CustomColumnHeading'; import { Title } from '../../components/Title'; import { cellStyles, UniversalDataGrid } from '../../components/Universal-DataGrid'; @@ -35,7 +35,7 @@ const getCellStyles = (theme: Theme, row: MixnodeRowType, textColor?: string): S ...getCellFontStyle(theme, row, textColor), }); -export const PageMixnodes: React.FC = () => { +export const PageMixnodes: FCWithChildren = () => { const { mixnodes, fetchMixnodes } = useMainContext(); const [filteredMixnodes, setFilteredMixnodes] = React.useState<MixNodeResponse>([]); const [pageSize, setPageSize] = React.useState<string>('10'); @@ -85,6 +85,7 @@ export const PageMixnodes: React.FC = () => { { field: 'mix_id', headerName: 'Mix ID', + disableColumnMenu: true, renderHeader: () => <CustomColumnHeading headingTitle="Mix ID" />, headerClassName: 'MuiDataGrid-header-override', width: 100, @@ -103,6 +104,7 @@ export const PageMixnodes: React.FC = () => { { field: 'identity_key', headerName: 'Identity Key', + disableColumnMenu: true, renderHeader: () => <CustomColumnHeading headingTitle="Identity Key" />, headerClassName: 'MuiDataGrid-header-override', width: 170, @@ -128,6 +130,7 @@ export const PageMixnodes: React.FC = () => { { field: 'bond', headerName: 'Stake', + disableColumnMenu: true, renderHeader: () => <CustomColumnHeading headingTitle="Stake" />, type: 'number', headerClassName: 'MuiDataGrid-header-override', @@ -146,10 +149,11 @@ export const PageMixnodes: React.FC = () => { { field: 'stake_saturation', headerName: 'Stake Saturation', + disableColumnMenu: true, renderHeader: () => ( <CustomColumnHeading headingTitle="Stake Saturation" - tooltipInfo="Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 750k NYM, computed as S/K where S is total amount of tokens available to stakeholders and K is the number of nodes in the reward set." + tooltipInfo="Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is 730k NYM, computed as S/K where S is target amount of tokens staked in the network and K is the number of nodes in the reward set." /> ), headerClassName: 'MuiDataGrid-header-override', @@ -164,13 +168,14 @@ export const PageMixnodes: React.FC = () => { component={RRDLink} to={`/network-components/mixnode/${params.row.mix_id}`} > - {`${params.value.toFixed(2)} %`} + {`${params.value} %`} </MuiLink> ), }, { field: 'pledge_amount', headerName: 'Bond', + disableColumnMenu: true, width: 175, headerClassName: 'MuiDataGrid-header-override', renderHeader: () => <CustomColumnHeading headingTitle="Bond" tooltipInfo="Node operator's share of stake." />, @@ -189,6 +194,7 @@ export const PageMixnodes: React.FC = () => { { field: 'profit_percentage', headerName: 'Profit Margin', + disableColumnMenu: true, renderHeader: () => ( <CustomColumnHeading headingTitle="Profit Margin" @@ -220,6 +226,7 @@ export const PageMixnodes: React.FC = () => { headerClassName: 'MuiDataGrid-header-override', width: 170, headerAlign: 'left', + disableColumnMenu: true, renderCell: (params: GridRenderCellParams) => ( <MuiLink sx={{ ...getCellStyles(theme, params.row), textAlign: 'left' }} @@ -231,12 +238,13 @@ export const PageMixnodes: React.FC = () => { ), }, { - field: 'avg_uptime', + field: 'node_performance', headerName: 'Routing Score', + disableColumnMenu: true, renderHeader: () => ( <CustomColumnHeading headingTitle="Routing Score" - tooltipInfo="Node’s routing score is relative to that of the network. Each time a node is tested, the test packets have to go through the full path of the network (a gateway + 3 nodes). If a node in the path drop packets it will affect the score of other nodes in the test." + tooltipInfo="Mixnode's most recent score (measured in the last 15 minutes). Routing score is relative to that of the network. Each time a gateway is tested, the test packets have to go through the full path of the network (gateway + 3 nodes). If a node in the path drop packets it will affect the score of the gateway and other nodes in the test." /> ), headerClassName: 'MuiDataGrid-header-override', @@ -255,13 +263,14 @@ export const PageMixnodes: React.FC = () => { { field: 'owner', headerName: 'Owner', + disableColumnMenu: true, renderHeader: () => <CustomColumnHeading headingTitle="Owner" />, width: 120, headerAlign: 'left', headerClassName: 'MuiDataGrid-header-override', renderCell: (params: GridRenderCellParams) => ( <MuiLink - href={`${BIG_DIPPER}/account/${params.value}`} + href={`${NYM_BIG_DIPPER}/account/${params.value}`} target="_blank" sx={getCellStyles(theme, params.row)} data-testid="big-dipper-link" @@ -274,6 +283,7 @@ export const PageMixnodes: React.FC = () => { field: 'location', headerName: 'Location', renderHeader: () => <CustomColumnHeading headingTitle="Location" />, + disableColumnMenu: true, width: 120, headerAlign: 'left', headerClassName: 'MuiDataGrid-header-override', @@ -303,6 +313,7 @@ export const PageMixnodes: React.FC = () => { field: 'host', headerName: 'Host', renderHeader: () => <CustomColumnHeading headingTitle="Host" />, + disableColumnMenu: true, headerClassName: 'MuiDataGrid-header-override', width: 130, headerAlign: 'left', @@ -321,7 +332,6 @@ export const PageMixnodes: React.FC = () => { const handlePageSize = (event: SelectChangeEvent<string>) => { setPageSize(event.target.value); }; - return ( <> <Title text="Mixnodes" /> diff --git a/explorer/src/pages/MixnodesMap/index.tsx b/explorer/src/pages/MixnodesMap/index.tsx index e2bd5210ff..c6148cb894 100644 --- a/explorer/src/pages/MixnodesMap/index.tsx +++ b/explorer/src/pages/MixnodesMap/index.tsx @@ -10,7 +10,7 @@ import { WorldMap } from '../../components/WorldMap'; import { useMainContext } from '../../context/main'; import { CountryDataRowType, countryDataToGridRow } from '../../utils'; -export const PageMixnodesMap: React.FC = () => { +export const PageMixnodesMap: FCWithChildren = () => { const { countryData } = useMainContext(); const [pageSize, setPageSize] = React.useState<string>('10'); const [formattedCountries, setFormattedCountries] = React.useState<CountryDataRowType[]>([]); @@ -115,5 +115,10 @@ export const PageMixnodesMap: React.FC = () => { </Box> ); } - return <Alert severity="error">{countryData?.error}</Alert>; + + if (countryData?.error) { + return <Alert severity="error">{countryData.error.message}</Alert>; + } + + return null; }; diff --git a/explorer/src/pages/Overview/index.tsx b/explorer/src/pages/Overview/index.tsx index 06e3d241bc..acf03ad77a 100644 --- a/explorer/src/pages/Overview/index.tsx +++ b/explorer/src/pages/Overview/index.tsx @@ -3,6 +3,7 @@ import { Box, Grid, Link, Typography } from '@mui/material'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import { useTheme } from '@mui/material/styles'; import { useNavigate } from 'react-router-dom'; +import { PeopleAlt } from '@mui/icons-material'; import { WorldMap } from '../../components/WorldMap'; import { useMainContext } from '../../context/main'; import { formatNumber } from '../../utils'; @@ -15,10 +16,10 @@ import { ContentCard } from '../../components/ContentCard'; import { StatsCard } from '../../components/StatsCard'; import { Icons } from '../../components/Icons'; -export const PageOverview: React.FC = () => { +export const PageOverview: FCWithChildren = () => { const theme = useTheme(); const navigate = useNavigate(); - const { summaryOverview, gateways, validators, block, countryData } = useMainContext(); + const { summaryOverview, gateways, validators, block, countryData, serviceProviders } = useMainContext(); return ( <Box component="main" sx={{ flexGrow: 1 }}> <Grid> @@ -61,7 +62,7 @@ export const PageOverview: React.FC = () => { </> )} {gateways && ( - <Grid item xs={12} md={6}> + <Grid item xs={12} md={4}> <StatsCard onClick={() => navigate('/network-components/gateways')} title="Gateways" @@ -71,9 +72,19 @@ export const PageOverview: React.FC = () => { /> </Grid> )} - + {serviceProviders && ( + <Grid item xs={12} md={4}> + <StatsCard + onClick={() => navigate('/network-components/service-providers')} + title="Service providers" + icon={<PeopleAlt />} + count={serviceProviders.data?.items.length} + errorMsg={summaryOverview?.error} + /> + </Grid> + )} {validators && ( - <Grid item xs={12} md={6}> + <Grid item xs={12} md={4}> <StatsCard onClick={() => window.open(`${BIG_DIPPER}/validators`)} title="Validators" diff --git a/explorer/src/pages/ServiceProviders/index.tsx b/explorer/src/pages/ServiceProviders/index.tsx new file mode 100644 index 0000000000..512042a4e5 --- /dev/null +++ b/explorer/src/pages/ServiceProviders/index.tsx @@ -0,0 +1,74 @@ +import React, { useEffect, useState } from 'react'; +import { Card, FormControl, Grid, MenuItem, Select, SelectChangeEvent } from '@mui/material'; +import { Api } from '../../api'; +import { TableToolbar } from '../../components/TableToolbar'; +import { Title } from '../../components/Title'; +import { UniversalDataGrid } from '../../components/Universal-DataGrid'; +import { DirectoryService } from '../../typeDefs/explorer-api'; + +const columns = [ + { + headerName: 'Client ID', + field: 'address', + disableColumnMenu: true, + flex: 1, + }, +]; + +const SupportedApps = () => { + const [selected, setSelected] = useState<string>(''); + const handleChange = (e: SelectChangeEvent) => setSelected(e.target.value); + return ( + <FormControl size="small"> + <Select value={selected} onChange={handleChange} displayEmpty sx={{ mr: 2 }}> + <MenuItem value="">Supported Apps</MenuItem> + <MenuItem>Keybase</MenuItem> + <MenuItem>Telegram</MenuItem> + <MenuItem>Electrum</MenuItem> + <MenuItem>Blockstream Green</MenuItem> + </Select> + </FormControl> + ); +}; + +export const ServiceProviders = () => { + const [serviceProviders, setServiceProviders] = useState<DirectoryService>(); + const [pageSize, setPageSize] = React.useState('10'); + + const getServiceproviders = async () => { + const [data] = await Api.fetchServiceProviders(); + setServiceProviders(data); + }; + + useEffect(() => { + getServiceproviders(); + }, []); + + const handleOnPageSizeChange = (event: SelectChangeEvent<string>) => { + setPageSize(event.target.value); + }; + + if (!serviceProviders) return null; + + return ( + <> + <Title text="Service Providers" /> + <Grid container> + <Grid item xs={12}> + <Card + sx={{ + padding: 2, + }} + > + <TableToolbar + onChangePageSize={handleOnPageSizeChange} + pageSize={pageSize} + childrenBefore={<SupportedApps />} + /> + <UniversalDataGrid pagination rows={serviceProviders.items} columns={columns} pageSize={pageSize} /> + </Card> + </Grid> + </Grid> + </> + ); +}; diff --git a/explorer/src/routes/index.tsx b/explorer/src/routes/index.tsx index 2ac30a6d41..57e015495d 100644 --- a/explorer/src/routes/index.tsx +++ b/explorer/src/routes/index.tsx @@ -5,11 +5,11 @@ import { PageMixnodesMap } from '../pages/MixnodesMap'; import { Page404 } from '../pages/404'; import { NetworkComponentsRoutes } from './network-components'; -export const Routes: React.FC = () => ( +export const Routes: FCWithChildren = () => ( <ReactRouterRoutes> <Route path="/" element={<PageOverview />} /> <Route path="/network-components/*" element={<NetworkComponentsRoutes />} /> <Route path="/nodemap" element={<PageMixnodesMap />} /> - <Route path="*" element={Page404} /> + <Route path="*" element={<Page404 />} /> </ReactRouterRoutes> ); diff --git a/explorer/src/routes/network-components.tsx b/explorer/src/routes/network-components.tsx index 0118641b2e..266a75aa2f 100644 --- a/explorer/src/routes/network-components.tsx +++ b/explorer/src/routes/network-components.tsx @@ -5,15 +5,16 @@ import { PageGateways } from '../pages/Gateways'; import { PageGatewayDetail } from '../pages/GatewayDetail'; import { PageMixnodeDetail } from '../pages/MixnodeDetail'; import { PageMixnodes } from '../pages/Mixnodes'; +import { ServiceProviders } from '../pages/ServiceProviders'; -const ValidatorRoute: React.FC = () => { +const ValidatorRoute: FCWithChildren = () => { const navigate = useNavigate(); window.open(`${BIG_DIPPER}/validators`); navigate(-1); return null; }; -export const NetworkComponentsRoutes: React.FC = () => ( +export const NetworkComponentsRoutes: FCWithChildren = () => ( <ReactRouterRoutes> <Route path="mixnodes/:status" element={<PageMixnodes />} /> <Route path="mixnodes" element={<PageMixnodes />} /> @@ -21,6 +22,6 @@ export const NetworkComponentsRoutes: React.FC = () => ( <Route path="gateways" element={<PageGateways />} /> <Route path="gateway/:id" element={<PageGatewayDetail />} /> <Route path="validators" element={<ValidatorRoute />} /> - <Route path="gateways/:id" element={<h1> Specific Gateways ID</h1>} /> + <Route path="service-providers" element={<ServiceProviders />} /> </ReactRouterRoutes> ); diff --git a/explorer/src/styles.css b/explorer/src/styles.css index e06c314f6b..818211ccea 100644 --- a/explorer/src/styles.css +++ b/explorer/src/styles.css @@ -4,31 +4,27 @@ the theme declaration in index.tsx or the style prop in <DataGrid /> */ -.MuiDataGrid-sortIcon, .MuiDataGrid-menuIcon, .MuiDataGrid-iconButtonContainer{ - display: none !important; -} - .MuiDataGrid-columnSeparator { - visibility: hidden; + visibility: hidden; } /* TODO - this should be managed somehow in MUI DataGrid but documentation doesnt offer a way to do it. Possibly only included in Data Grid Pro package */ .MuiDataGrid-header-override { - height: 55px; - min-height: 55px; + height: 55px; + min-height: 55px; } /* Again, no offered way to add sx to this specific div to kill the padding which puts it out of sync with other (sx styled) cells */ div div.MuiDataGrid-root .MuiDataGrid-columnHeaderTitleContainer { - padding-left: 0; + padding-left: 0; } @media screen and (max-width: 900px) { - .MuiDrawer-paperAnchorLeft { - min-width: 100vw; - margin-top: 58px; - } + .MuiDrawer-paperAnchorLeft { + min-width: 100vw; + margin-top: 58px; + } } diff --git a/explorer/src/theme/index.tsx b/explorer/src/theme/index.tsx index 00b498fbc3..3542bd9c9a 100644 --- a/explorer/src/theme/index.tsx +++ b/explorer/src/theme/index.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { NymNetworkExplorerThemeProvider } from '@nymproject/mui-theme'; import { useMainContext } from '../context/main'; -export const NetworkExplorerThemeProvider: React.FC = ({ children }) => { +export const NetworkExplorerThemeProvider: FCWithChildren = ({ children }) => { const { mode } = useMainContext(); return <NymNetworkExplorerThemeProvider mode={mode}>{children}</NymNetworkExplorerThemeProvider>; }; diff --git a/explorer/src/typeDefs/explorer-api.ts b/explorer/src/typeDefs/explorer-api.ts index f8350775c4..e6cc617ddb 100644 --- a/explorer/src/typeDefs/explorer-api.ts +++ b/explorer/src/typeDefs/explorer-api.ts @@ -77,17 +77,19 @@ export interface MixNodeResponseItem { status: MixnodeStatus; location: { country_name: string; - lat: number; - lng: number; + latitude?: number; + longitude?: number; three_letter_iso_country_code: string; two_letter_iso_country_code: string; }; mix_node: MixNode; avg_uptime: number; + node_performance: NodePerformance; stake_saturation: number; uncapped_saturation: number; operating_cost: Amount; profit_margin_percent: string; + blacklisted: boolean; } export type MixNodeResponse = MixNodeResponseItem[]; @@ -114,17 +116,29 @@ export interface StatsResponse { packets_explicitly_dropped_since_last_update: number; } +export interface NodePerformance { + most_recent: string; + last_hour: string; + last_24h: string; +} + export type MixNodeHistoryResponse = StatsResponse; -export interface GatewayResponseItem { +export interface GatewayBond { block_height: number; pledge_amount: Amount; total_delegation: Amount; owner: string; gateway: Gateway; + node_performance: NodePerformance; } -export type GatewayResponse = GatewayResponseItem[]; +export interface GatewayBondAnnotated { + gateway_bond: GatewayBond; + node_performance: NodePerformance; +} + +export type GatewayResponse = GatewayBond[]; export interface GatewayReportResponse { identity: string; @@ -224,3 +238,18 @@ export type MixNodeEconomicDynamicsStatsResponse = { estimated_delegators_reward: number; current_interval_uptime: number; }; + +export type Environment = 'mainnet' | 'sandbox' | 'qa'; + +export type DirectoryServiceProvider = { + id: string; + description: string; + address: string; + gateway: string; +}; + +export type DirectoryService = { + id: string; + descrtiption: string; + items: DirectoryServiceProvider[]; +}; diff --git a/explorer/src/typings/FC.d.ts b/explorer/src/typings/FC.d.ts new file mode 100644 index 0000000000..08ebdfa298 --- /dev/null +++ b/explorer/src/typings/FC.d.ts @@ -0,0 +1 @@ +declare type FCWithChildren<P = {}> = React.FC<React.PropsWithChildren<P>>; diff --git a/explorer/src/utils/index.ts b/explorer/src/utils/index.ts index 1065c000ae..4f922d870d 100644 --- a/explorer/src/utils/index.ts +++ b/explorer/src/utils/index.ts @@ -1,7 +1,9 @@ /* eslint-disable camelcase */ import { MutableRefObject } from 'react'; +import { Theme } from '@mui/material/styles'; import { registerLocale, getName } from 'i18n-iso-countries'; import { CountryData } from '../typeDefs/explorer-api'; +import { EconomicsRowsType } from '../components/MixNodes/Economics/types'; registerLocale(require('i18n-iso-countries/langs/en.json')); @@ -53,3 +55,29 @@ export const splice = (start: number, deleteCount: number, address?: string): st * @returns A stringified integer */ export const toPercentIntegerString = (value: string) => Math.round(Number(value) * 100).toString(); + +export const textColour = (value: EconomicsRowsType, field: string, theme: Theme) => { + const progressBarValue = value?.progressBarValue || 0; + const fieldValue = value.value; + + if (progressBarValue > 100) { + return theme.palette.warning.main; + } + if (field === 'selectionChance') { + // TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow + switch (fieldValue) { + case 'High': + case 'VeryHigh': + return theme.palette.nym.networkExplorer.selectionChance.overModerate; + case 'Good': + case 'Moderate': + return theme.palette.nym.networkExplorer.selectionChance.moderate; + case 'Low': + case 'VeryLow': + return theme.palette.nym.networkExplorer.selectionChance.underModerate; + default: + return theme.palette.nym.wallet.fee; + } + } + return theme.palette.nym.wallet.fee; +}; diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 528072ed96..75b3e5b078 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-gateway" -version = "1.1.4" +version = "1.1.13" authors = [ "Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>", @@ -16,21 +16,22 @@ rust-version = "1.56" [dependencies] anyhow = "1.0.53" -async-trait = { version = "0.1.51" } +async-trait = { workspace = true } bip39 = "1.0.1" bs58 = "0.4.0" -clap = { version = "3.2", features = ["cargo", "derive"] } +clap = { version = "4.0", features = ["cargo", "derive"] } colored = "2.0" dashmap = "4.0" dirs = "4.0" -dotenv = "0.15.0" +dotenvy = { workspace = true } futures = "0.3" humantime-serde = "1.0.1" -log = "0.4" +lazy_static = "1.4.0" +log = { workspace = true } once_cell = "1.7.2" pretty_env_logger = "0.4" rand = "0.7" -serde = { version = "1.0.104", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "sqlite", @@ -39,56 +40,45 @@ sqlx = { version = "0.5", features = [ ] } subtle-encoding = { version = "0.5", features = ["bech32-preview"] } thiserror = "1" -tokio = { version = "1.21.2", features = [ +tokio = { version = "1.24.1", features = [ "rt-multi-thread", "net", "signal", "fs", ] } -tokio-stream = { version = "0.1.9", features = ["fs"] } +tokio-stream = { version = "0.1.11", features = ["fs"] } tokio-tungstenite = "0.14" -tokio-util = { version = "0.7.3", features = ["codec"] } +tokio-util = { version = "0.7.4", features = ["codec"] } url = { version = "2.2", features = ["serde"] } # internal -coconut-interface = { path = "../common/coconut-interface", optional = true } -credentials = { path = "../common/credentials" } -config = { path = "../common/config" } -crypto = { path = "../common/crypto" } -completions = { path = "../common/completions" } -logging = { path = "../common/logging"} +nym-coconut-interface = { path = "../common/coconut-interface" } +nym-credentials = { path = "../common/credentials" } +nym-config = { path = "../common/config" } +nym-crypto = { path = "../common/crypto" } +nym-bin-common = { path = "../common/bin-common" } gateway-requests = { path = "gateway-requests" } mixnet-client = { path = "../common/client-libs/mixnet-client" } mixnode-common = { path = "../common/mixnode-common" } -network-defaults = { path = "../common/network-defaults" } -nymsphinx = { path = "../common/nymsphinx" } -pemstore = { path = "../common/pemstore" } -statistics-common = { path = "../common/statistics" } -validator-api-requests = { path = "../validator-api/validator-api-requests" } +nym-network-defaults = { path = "../common/network-defaults" } +nym-sphinx = { path = "../common/nymsphinx" } +nym-pemstore = { path = "../common/pemstore" } +nym-statistics-common = { path = "../common/statistics" } +nym-api-requests = { path = "../nym-api/nym-api-requests" } +nym-task = { path = "../common/task" } validator-client = { path = "../common/client-libs/validator-client", features = [ - "nymd-client", + "nyxd-client", ] } -version-checker = { path = "../common/version-checker" } +nym-types = { path = "../common/types" } +serde_json = "1" +atty = "0.2" -[features] -coconut = [ - "coconut-interface", - "gateway-requests/coconut", - "credentials/coconut", - "validator-api-requests/coconut", -] [build-dependencies] -tokio = { version = "1.21.2", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "sqlite", "macros", "migrate", ] } -vergen = { version = "5", default-features = false, features = [ - "build", - "git", - "rustc", - "cargo", -] } diff --git a/gateway/build.rs b/gateway/build.rs index 16bdf16335..98ad043036 100644 --- a/gateway/build.rs +++ b/gateway/build.rs @@ -1,6 +1,5 @@ use sqlx::{Connection, SqliteConnection}; use std::env; -use vergen::{vergen, Config}; #[tokio::main] async fn main() { @@ -23,6 +22,4 @@ async fn main() { // 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); - - vergen(Config::default()).expect("failed to extract build metadata") } diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index 9501755306..3fce04a8ae 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -12,21 +12,18 @@ edition = "2021" [dependencies] bs58 = "0.4.0" futures = "0.3.15" -log = "0.4.14" -nymsphinx = { path = "../../common/nymsphinx" } +log = { workspace = true } +nym-sphinx = { path = "../../common/nymsphinx" } rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -crypto = { path = "../../common/crypto" } -pemstore = { path = "../../common/pemstore" } +nym-crypto = { path = "../../common/crypto" } +nym-pemstore = { path = "../../common/pemstore" } -coconut-interface = { path = "../../common/coconut-interface", optional = true } -credentials = { path = "../../common/credentials" } - -[features] -coconut = ["coconut-interface", "credentials/coconut"] +nym-coconut-interface = { path = "../../common/coconut-interface" } +nym-credentials = { path = "../../common/credentials" } [dependencies.tungstenite] version = "0.13.0" diff --git a/gateway/gateway-requests/src/authentication/encrypted_address.rs b/gateway/gateway-requests/src/authentication/encrypted_address.rs index c56d208013..2d94520ac1 100644 --- a/gateway/gateway-requests/src/authentication/encrypted_address.rs +++ b/gateway/gateway-requests/src/authentication/encrypted_address.rs @@ -3,9 +3,9 @@ use crate::iv::IV; use crate::registration::handshake::shared_key::SharedKeys; -use crypto::symmetric::stream_cipher; -use nymsphinx::params::GatewayEncryptionAlgorithm; -use nymsphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; +use nym_crypto::symmetric::stream_cipher; +use nym_sphinx::params::GatewayEncryptionAlgorithm; +use nym_sphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; use thiserror::Error; pub const ENCRYPTED_ADDRESS_SIZE: usize = DESTINATION_ADDRESS_LENGTH; diff --git a/gateway/gateway-requests/src/iv.rs b/gateway/gateway-requests/src/iv.rs index 0a7d428f1c..d7ab8d1c19 100644 --- a/gateway/gateway-requests/src/iv.rs +++ b/gateway/gateway-requests/src/iv.rs @@ -1,9 +1,9 @@ // Copyright 2020 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crypto::generic_array::{typenum::Unsigned, GenericArray}; -use crypto::symmetric::stream_cipher::{random_iv, IvSizeUser, IV as CryptoIV}; -use nymsphinx::params::GatewayEncryptionAlgorithm; +use nym_crypto::generic_array::{typenum::Unsigned, GenericArray}; +use nym_crypto::symmetric::stream_cipher::{random_iv, IvSizeUser, IV as CryptoIV}; +use nym_sphinx::params::GatewayEncryptionAlgorithm; use rand::{CryptoRng, RngCore}; use thiserror::Error; diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs index d1c1861a8a..a2404d6c4b 100644 --- a/gateway/gateway-requests/src/lib.rs +++ b/gateway/gateway-requests/src/lib.rs @@ -1,10 +1,10 @@ // Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -pub use crypto::generic_array; -use crypto::hmac::HmacOutput; -use crypto::OutputSizeUser; -use nymsphinx::params::GatewayIntegrityHmacAlgorithm; +pub use nym_crypto::generic_array; +use nym_crypto::hmac::HmacOutput; +use nym_crypto::OutputSizeUser; +use nym_sphinx::params::GatewayIntegrityHmacAlgorithm; pub use types::*; pub mod authentication; diff --git a/gateway/gateway-requests/src/registration/handshake/client.rs b/gateway/gateway-requests/src/registration/handshake/client.rs index 784364ebe2..4e3c865553 100644 --- a/gateway/gateway-requests/src/registration/handshake/client.rs +++ b/gateway/gateway-requests/src/registration/handshake/client.rs @@ -4,12 +4,12 @@ use crate::registration::handshake::shared_key::SharedKeys; use crate::registration::handshake::state::State; use crate::registration::handshake::{error::HandshakeError, WsItem}; -use crypto::asymmetric::encryption::PUBLIC_KEY_SIZE; -use crypto::asymmetric::identity::SIGNATURE_LENGTH; -use crypto::asymmetric::{encryption, identity}; use futures::future::BoxFuture; use futures::task::{Context, Poll}; use futures::{Future, Sink, Stream}; +use nym_crypto::asymmetric::encryption::PUBLIC_KEY_SIZE; +use nym_crypto::asymmetric::identity::SIGNATURE_LENGTH; +use nym_crypto::asymmetric::{encryption, identity}; use rand::{CryptoRng, RngCore}; use std::pin::Pin; use tungstenite::Message as WsMessage; @@ -22,7 +22,7 @@ impl<'a> ClientHandshake<'a> { pub(crate) fn new<S>( rng: &mut (impl RngCore + CryptoRng), ws_stream: &'a mut S, - identity: &'a crypto::asymmetric::identity::KeyPair, + identity: &'a nym_crypto::asymmetric::identity::KeyPair, gateway_pubkey: identity::PublicKey, ) -> Self where diff --git a/gateway/gateway-requests/src/registration/handshake/error.rs b/gateway/gateway-requests/src/registration/handshake/error.rs index 81b2c2d0c7..798cd2ec55 100644 --- a/gateway/gateway-requests/src/registration/handshake/error.rs +++ b/gateway/gateway-requests/src/registration/handshake/error.rs @@ -1,7 +1,7 @@ // Copyright 2020 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crypto::asymmetric::identity; +use nym_crypto::asymmetric::identity; use thiserror::Error; #[derive(Debug, Error)] diff --git a/gateway/gateway-requests/src/registration/handshake/gateway.rs b/gateway/gateway-requests/src/registration/handshake/gateway.rs index ace136d290..2afebbc115 100644 --- a/gateway/gateway-requests/src/registration/handshake/gateway.rs +++ b/gateway/gateway-requests/src/registration/handshake/gateway.rs @@ -4,10 +4,10 @@ use crate::registration::handshake::shared_key::SharedKeys; use crate::registration::handshake::state::State; use crate::registration::handshake::{error::HandshakeError, WsItem}; -use crypto::asymmetric::encryption; use futures::future::BoxFuture; use futures::task::{Context, Poll}; use futures::{Future, Sink, Stream}; +use nym_crypto::asymmetric::encryption; use rand::{CryptoRng, RngCore}; use std::pin::Pin; use tungstenite::Message as WsMessage; @@ -20,7 +20,7 @@ impl<'a> GatewayHandshake<'a> { pub(crate) fn new<S>( rng: &mut (impl RngCore + CryptoRng), ws_stream: &'a mut S, - identity: &'a crypto::asymmetric::identity::KeyPair, + identity: &'a nym_crypto::asymmetric::identity::KeyPair, received_init_payload: Vec<u8>, ) -> Self where diff --git a/gateway/gateway-requests/src/registration/handshake/mod.rs b/gateway/gateway-requests/src/registration/handshake/mod.rs index 0b3b95bf7f..edf8a6fae0 100644 --- a/gateway/gateway-requests/src/registration/handshake/mod.rs +++ b/gateway/gateway-requests/src/registration/handshake/mod.rs @@ -6,8 +6,8 @@ use self::error::HandshakeError; #[cfg(not(target_arch = "wasm32"))] use self::gateway::GatewayHandshake; pub use self::shared_key::{SharedKeySize, SharedKeys}; -use crypto::asymmetric::identity; use futures::{Sink, Stream}; +use nym_crypto::asymmetric::identity; use rand::{CryptoRng, RngCore}; use tungstenite::{Error as WsError, Message as WsMessage}; diff --git a/gateway/gateway-requests/src/registration/handshake/shared_key.rs b/gateway/gateway-requests/src/registration/handshake/shared_key.rs index 3fc014db51..2f56c0ac97 100644 --- a/gateway/gateway-requests/src/registration/handshake/shared_key.rs +++ b/gateway/gateway-requests/src/registration/handshake/shared_key.rs @@ -1,16 +1,16 @@ -// Copyright 2020 - Nym Technologies SA <contact@nymtech.net> +// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 use crate::{GatewayMacSize, GatewayRequestsError}; -use crypto::generic_array::{ +use nym_crypto::generic_array::{ typenum::{Sum, Unsigned, U16}, GenericArray, }; -use crypto::hmac::{compute_keyed_hmac, recompute_keyed_hmac_and_verify_tag}; -use crypto::symmetric::stream_cipher::{self, CipherKey, KeySizeUser, IV}; -use nymsphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm}; -use pemstore::traits::PemStorableKey; -use std::fmt::{self, Display, Formatter}; +use nym_crypto::hmac::{compute_keyed_hmac, recompute_keyed_hmac_and_verify_tag}; +use nym_crypto::symmetric::stream_cipher::{self, CipherKey, KeySizeUser, IV}; +use nym_pemstore::traits::PemStorableKey; +use nym_sphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm}; +use thiserror::Error; // shared key is as long as the encryption key and the MAC key combined. pub type SharedKeySize = Sum<EncryptionKeySize, MacKeySize>; @@ -28,37 +28,23 @@ pub struct SharedKeys { mac_key: MacKey, } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum SharedKeyConversionError { - DecodeError(bs58::decode::Error), - BytesOfInvalidLengthError, - StringOfInvalidLengthError, + #[error("the string representation of the shared keys was malformed - {0}")] + DecodeError(#[from] bs58::decode::Error), + #[error( + "the received shared keys had invalid size. Got: {received}, but expected: {expected}" + )] + InvalidSharedKeysSize { received: usize, expected: usize }, } -impl Display for SharedKeyConversionError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - SharedKeyConversionError::DecodeError(err) => write!( - f, - "encountered error while decoding the byte sequence: {}", - err - ), - SharedKeyConversionError::BytesOfInvalidLengthError => { - write!(f, "provided bytes have invalid length") - } - SharedKeyConversionError::StringOfInvalidLengthError => { - write!(f, "provided string has invalid length") - } - } - } -} - -impl std::error::Error for SharedKeyConversionError {} - impl SharedKeys { pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, SharedKeyConversionError> { if bytes.len() != SharedKeySize::to_usize() { - return Err(SharedKeyConversionError::BytesOfInvalidLengthError); + return Err(SharedKeyConversionError::InvalidSharedKeysSize { + received: bytes.len(), + expected: SharedKeySize::to_usize(), + }); } let encryption_key = @@ -157,15 +143,7 @@ impl SharedKeys { pub fn try_from_base58_string<S: Into<String>>( val: S, ) -> Result<Self, SharedKeyConversionError> { - let decoded = match bs58::decode(val.into()).into_vec() { - Ok(decoded) => decoded, - Err(err) => return Err(SharedKeyConversionError::DecodeError(err)), - }; - - if decoded.len() != SharedKeySize::to_usize() { - return Err(SharedKeyConversionError::StringOfInvalidLengthError); - } - + let decoded = bs58::decode(val.into()).into_vec()?; SharedKeys::try_from_bytes(&decoded) } diff --git a/gateway/gateway-requests/src/registration/handshake/state.rs b/gateway/gateway-requests/src/registration/handshake/state.rs index 1560b5fd9f..97f88c570a 100644 --- a/gateway/gateway-requests/src/registration/handshake/state.rs +++ b/gateway/gateway-requests/src/registration/handshake/state.rs @@ -5,15 +5,15 @@ use crate::registration::handshake::error::HandshakeError; use crate::registration::handshake::shared_key::{SharedKeySize, SharedKeys}; use crate::registration::handshake::WsItem; use crate::types; -use crypto::{ +use futures::{Sink, SinkExt, Stream, StreamExt}; +use log::*; +use nym_crypto::{ asymmetric::{encryption, identity}, generic_array::typenum::Unsigned, hkdf, symmetric::stream_cipher, }; -use futures::{Sink, SinkExt, Stream, StreamExt}; -use log::*; -use nymsphinx::params::{GatewayEncryptionAlgorithm, GatewaySharedKeyHkdfAlgorithm}; +use nym_sphinx::params::{GatewayEncryptionAlgorithm, GatewaySharedKeyHkdfAlgorithm}; use rand::{CryptoRng, RngCore}; use std::convert::{TryFrom, TryInto}; use tungstenite::Message as WsMessage; diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index f73f85bca5..0685c0d473 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -5,14 +5,15 @@ use crate::authentication::encrypted_address::EncryptedAddressBytes; use crate::iv::IV; use crate::registration::handshake::SharedKeys; use crate::{GatewayMacSize, PROTOCOL_VERSION}; -use crypto::generic_array::typenum::Unsigned; -use crypto::hmac::recompute_keyed_hmac_and_verify_tag; -use crypto::symmetric::stream_cipher; -use nymsphinx::addressing::nodes::NymNodeRoutingAddressError; -use nymsphinx::forwarding::packet::{MixPacket, MixPacketFormattingError}; -use nymsphinx::params::packet_sizes::PacketSize; -use nymsphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm}; -use nymsphinx::DestinationAddressBytes; +use nym_coconut_interface::Credential; +use nym_crypto::generic_array::typenum::Unsigned; +use nym_crypto::hmac::recompute_keyed_hmac_and_verify_tag; +use nym_crypto::symmetric::stream_cipher; +use nym_sphinx::addressing::nodes::NymNodeRoutingAddressError; +use nym_sphinx::forwarding::packet::{MixPacket, MixPacketFormattingError}; +use nym_sphinx::params::packet_sizes::PacketSize; +use nym_sphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm}; +use nym_sphinx::DestinationAddressBytes; use serde::{Deserialize, Serialize}; use std::{ convert::{TryFrom, TryInto}, @@ -20,11 +21,6 @@ use std::{ }; use tungstenite::protocol::Message; -#[cfg(feature = "coconut")] -use coconut_interface::Credential; -#[cfg(not(feature = "coconut"))] -use credentials::token::bandwidth::TokenCredential; - #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type", rename_all = "camelCase")] pub enum RegistrationHandshake { @@ -99,7 +95,7 @@ impl fmt::Display for GatewayRequestsError { MalformedSphinxPacket => write!(f, "received sphinx packet was malformed"), MalformedEncryption => write!(f, "the received encrypted data was malformed"), InvalidPacketMode => write!(f, "provided packet mode is invalid"), - InvalidMixPacket(err) => write!(f, "provided mix packet was malformed - {}", err) + InvalidMixPacket(err) => write!(f, "provided mix packet was malformed - {err}") } } } @@ -157,7 +153,6 @@ impl ClientControlRequest { } } - #[cfg(feature = "coconut")] pub fn new_enc_coconut_bandwidth_credential( credential: &Credential, shared_key: &SharedKeys, @@ -172,7 +167,6 @@ impl ClientControlRequest { } } - #[cfg(feature = "coconut")] pub fn try_from_enc_coconut_bandwidth_credential( enc_credential: Vec<u8>, shared_key: &SharedKeys, @@ -182,30 +176,6 @@ impl ClientControlRequest { Credential::from_bytes(&credential_bytes) .map_err(|_| GatewayRequestsError::MalformedEncryption) } - - #[cfg(not(feature = "coconut"))] - pub fn new_enc_token_bandwidth_credential( - credential: &TokenCredential, - shared_key: &SharedKeys, - iv: IV, - ) -> Self { - let enc_credential = shared_key.encrypt_and_tag(&credential.to_bytes(), Some(iv.inner())); - ClientControlRequest::BandwidthCredential { - enc_credential, - iv: iv.to_bytes(), - } - } - - #[cfg(not(feature = "coconut"))] - pub fn try_from_enc_token_bandwidth_credential( - enc_credential: Vec<u8>, - shared_key: &SharedKeys, - iv: IV, - ) -> Result<TokenCredential, GatewayRequestsError> { - let credential = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?; - TokenCredential::from_bytes(&credential) - .map_err(|_| GatewayRequestsError::MalformedEncryption) - } } impl From<ClientControlRequest> for Message { diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index dca393213a..02eefb6f69 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -1,13 +1,18 @@ -// Copyright 2020 - Nym Technologies SA <contact@nymtech.net> +// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 use crate::{ commands::{override_config, OverrideConfig}, config::{persistence::pathfinder::GatewayPathfinder, Config}, + OutputFormat, }; use clap::Args; -use config::NymConfig; -use crypto::asymmetric::{encryption, identity}; +use nym_config::NymConfig; +use nym_crypto::asymmetric::{encryption, identity}; +use std::error::Error; +use std::net::IpAddr; +use std::path::PathBuf; +use validator_client::nyxd; #[derive(Args, Clone)] pub struct Init { @@ -17,11 +22,11 @@ pub struct Init { /// The custom host on which the gateway will be running for receiving sphinx packets #[clap(long)] - host: String, + host: IpAddr, /// The wallet address you will use to bond this gateway, e.g. nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9 #[clap(long)] - wallet_address: String, + wallet_address: nyxd::AccountId, /// The port on which the gateway will be listening for sphinx packets #[clap(long)] @@ -33,29 +38,37 @@ pub struct Init { /// The host that will be reported to the directory server #[clap(long)] + // TODO: could this be changed to `Option<url::Url>`? announce_host: Option<String>, /// Path to sqlite database containing all gateway persistent data #[clap(long)] - datastore: Option<String>, + datastore: Option<PathBuf>, - /// Comma separated list of endpoints of the validators APIs - #[clap(long)] - validator_apis: Option<String>, + /// Comma separated list of endpoints of nym APIs + #[clap(long, alias = "validator_apis", value_delimiter = ',')] + // the alias here is included for backwards compatibility (1.1.4 and before) + nym_apis: Option<Vec<url::Url>>, /// Comma separated list of endpoints of the validator - #[clap(long)] - validators: Option<String>, + #[clap( + long, + alias = "validators", + alias = "nyxd_validators", + value_delimiter = ',', + hide = true + )] + // the alias here is included for backwards compatibility (1.1.4 and before) + nyxd_urls: Option<Vec<url::Url>>, /// Cosmos wallet mnemonic needed for double spending protection #[clap(long)] - mnemonic: Option<String>, + mnemonic: Option<bip39::Mnemonic>, /// Set this gateway to work only with coconut credentials; that would disallow clients to /// bypass bandwidth credential requirement - #[cfg(feature = "coconut")] - #[clap(long)] - only_coconut_credentials: bool, + #[clap(long, hide = true)] + only_coconut_credentials: Option<bool>, /// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server #[clap(long)] @@ -63,7 +76,7 @@ pub struct Init { /// URL where a statistics aggregator is running. The default value is a Nym aggregator server #[clap(long)] - statistics_service_url: Option<String>, + statistics_service_url: Option<url::Url>, } impl From<Init> for OverrideConfig { @@ -75,24 +88,23 @@ impl From<Init> for OverrideConfig { clients_port: init_config.clients_port, datastore: init_config.datastore, announce_host: init_config.announce_host, - validator_apis: init_config.validator_apis, - validators: init_config.validators, + nym_apis: init_config.nym_apis, mnemonic: init_config.mnemonic, - #[cfg(feature = "coconut")] - only_coconut_credentials: init_config.only_coconut_credentials, - enabled_statistics: init_config.enabled_statistics, statistics_service_url: init_config.statistics_service_url, + + nyxd_urls: init_config.nyxd_urls, + only_coconut_credentials: init_config.only_coconut_credentials, } } } -pub async fn execute(args: &Init) { - println!("Initialising gateway {}...", args.id); +pub async fn execute(args: Init, output: OutputFormat) -> Result<(), Box<dyn Error + Send + Sync>> { + eprintln!("Initialising gateway {}...", args.id); - let already_init = if Config::default_config_file_path(Some(&args.id)).exists() { - println!( + let already_init = if Config::default_config_file_path(&args.id).exists() { + eprintln!( "Gateway \"{}\" was already initialised before! Config information will be \ overwritten (but keys will be kept)!", args.id @@ -105,7 +117,7 @@ pub async fn execute(args: &Init) { let override_config_fields = OverrideConfig::from(args.clone()); // Initialising the config structure is just overriding a default constructed one - let config = override_config(Config::new(&args.id), override_config_fields); + let config = override_config(Config::new(&args.id), override_config_fields)?; // if gateway was already initialised, don't generate new keys if !already_init { @@ -114,42 +126,42 @@ pub async fn execute(args: &Init) { let identity_keys = identity::KeyPair::new(&mut rng); let sphinx_keys = encryption::KeyPair::new(&mut rng); let pathfinder = GatewayPathfinder::new_from_config(&config); - pemstore::store_keypair( + nym_pemstore::store_keypair( &sphinx_keys, - &pemstore::KeyPairPath::new( + &nym_pemstore::KeyPairPath::new( pathfinder.private_encryption_key().to_owned(), pathfinder.public_encryption_key().to_owned(), ), ) .expect("Failed to save sphinx keys"); - pemstore::store_keypair( + nym_pemstore::store_keypair( &identity_keys, - &pemstore::KeyPairPath::new( + &nym_pemstore::KeyPairPath::new( pathfinder.private_identity_key().to_owned(), pathfinder.public_identity_key().to_owned(), ), ) .expect("Failed to save identity keys"); - println!("Saved identity and mixnet sphinx keypairs"); + eprintln!("Saved identity and mixnet sphinx keypairs"); } let config_save_location = config.get_config_file_save_location(); config .save_to_file(None) .expect("Failed to save the config file"); - println!("Saved configuration file to {:?}", config_save_location); - println!("Gateway configuration completed.\n\n\n"); + eprintln!("Saved configuration file to {:?}", config_save_location); + eprintln!("Gateway configuration completed.\n\n\n"); - crate::node::create_gateway(config) + Ok(crate::node::create_gateway(config) .await - .print_node_details(); + .print_node_details(output)?) } #[cfg(test)] mod tests { - use network_defaults::var_names::BECH32_PREFIX; + use nym_network_defaults::var_names::BECH32_PREFIX; use crate::node::{storage::InMemStorage, Gateway}; @@ -159,24 +171,23 @@ mod tests { async fn create_gateway_with_in_mem_storage() { let args = Init { id: "foo-id".to_string(), - host: "foo-host".to_string(), - wallet_address: "n1z9egw0knv47nmur0p8vk4rcx59h9gg4zjx9ede".to_string(), + host: "1.1.1.1".parse().unwrap(), + wallet_address: "n1z9egw0knv47nmur0p8vk4rcx59h9gg4zjx9ede".parse().unwrap(), mix_port: Some(42), clients_port: Some(43), announce_host: Some("foo-announce-host".to_string()), - datastore: Some("foo-datastore".to_string()), - validator_apis: None, - validators: None, + datastore: Some("/foo-datastore".parse().unwrap()), + nym_apis: None, mnemonic: None, statistics_service_url: None, enabled_statistics: None, - #[cfg(feature = "coconut")] - only_coconut_credentials: false, + nyxd_urls: None, + only_coconut_credentials: None, }; std::env::set_var(BECH32_PREFIX, "n"); let config = Config::new(&args.id); - let config = override_config(config, OverrideConfig::from(args.clone())); + let config = override_config(config, OverrideConfig::from(args.clone())).unwrap(); let (identity_keys, sphinx_keys) = { let mut rng = rand::rngs::OsRng; diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 02dec84802..786b15d9c7 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -1,19 +1,19 @@ -// Copyright 2020 - Nym Technologies SA <contact@nymtech.net> +// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use std::{process, str::FromStr}; - +use crate::error::GatewayError; use crate::{config::Config, Cli}; use clap::CommandFactory; use clap::Subcommand; -use colored::Colorize; -use completions::{fig_generate, ArgShell}; -use config::parse_validators; -use crypto::bech32_address_validation; -use network_defaults::mainnet::read_var_if_not_default; -use network_defaults::var_names::{ - API_VALIDATOR, BECH32_PREFIX, CONFIGURED, NYMD_VALIDATOR, STATISTICS_SERVICE_DOMAIN_ADDRESS, -}; +use nym_bin_common::completions::{fig_generate, ArgShell}; +use nym_bin_common::version_checker; +use nym_config::OptionalSet; +use nym_network_defaults::var_names::NYXD; +use nym_network_defaults::var_names::{BECH32_PREFIX, NYM_API, STATISTICS_SERVICE_DOMAIN_ADDRESS}; +use std::error::Error; +use std::net::IpAddr; +use std::path::PathBuf; +use validator_client::nyxd::{self, AccountId}; pub(crate) mod init; pub(crate) mod node_details; @@ -46,52 +46,50 @@ pub(crate) enum Commands { } // Configuration that can be overridden. +#[derive(Default)] pub(crate) struct OverrideConfig { - host: Option<String>, - wallet_address: Option<String>, + host: Option<IpAddr>, + wallet_address: Option<nyxd::AccountId>, mix_port: Option<u16>, clients_port: Option<u16>, - datastore: Option<String>, + datastore: Option<PathBuf>, announce_host: Option<String>, enabled_statistics: Option<bool>, - statistics_service_url: Option<String>, - validator_apis: Option<String>, - validators: Option<String>, - mnemonic: Option<String>, - - #[cfg(feature = "coconut")] - only_coconut_credentials: bool, + statistics_service_url: Option<url::Url>, + nym_apis: Option<Vec<url::Url>>, + mnemonic: Option<bip39::Mnemonic>, + nyxd_urls: Option<Vec<url::Url>>, + only_coconut_credentials: Option<bool>, } -pub(crate) async fn execute(args: Cli) { +pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> { let bin_name = "nym-gateway"; - match &args.command { - Commands::Init(m) => init::execute(m).await, - Commands::NodeDetails(m) => node_details::execute(m).await, - Commands::Run(m) => run::execute(m).await, - Commands::Sign(m) => sign::execute(m), - Commands::Upgrade(m) => upgrade::execute(m).await, - Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name), - Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name), + let output = args.output(); + + match args.command { + Commands::Init(m) => init::execute(m, output.clone()).await?, + Commands::NodeDetails(m) => node_details::execute(m, output.clone()).await?, + Commands::Run(m) => run::execute(m, output.clone()).await?, + Commands::Sign(m) => sign::execute(m)?, + Commands::Upgrade(m) => upgrade::execute(&m).await, + Commands::Completions(s) => s.generate(&mut crate::Cli::command(), bin_name), + Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::command(), bin_name), } + Ok(()) } -pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config { +pub(crate) fn override_config( + mut config: Config, + args: OverrideConfig, +) -> Result<Config, GatewayError> { + // special case that I'm not sure could be easily handled with the trait let mut was_host_overridden = false; if let Some(host) = args.host { config = config.with_listening_address(host); was_host_overridden = true; } - if let Some(mix_port) = args.mix_port { - config = config.with_mix_port(mix_port); - } - - if let Some(clients_port) = args.clients_port { - config = config.with_clients_port(clients_port); - } - if let Some(announce_host) = args.announce_host { config = config.with_announce_address(announce_host); } else if was_host_overridden { @@ -99,103 +97,77 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi config = config.announce_host_from_listening_host(); } - if let Some(enabled_statistics) = args.enabled_statistics { - config = config.with_enabled_statistics(enabled_statistics); - } - - if let Some(raw_url) = args.statistics_service_url { - config = config.with_custom_statistics_service_url( - raw_url - .parse() - .expect("the provided statistics service url is invalid!"), + config = config + .with_optional(Config::with_mix_port, args.mix_port) + .with_optional(Config::with_clients_port, args.clients_port) + .with_optional_custom_env( + Config::with_custom_nym_apis, + args.nym_apis, + NYM_API, + nym_config::parse_urls, + ) + .with_optional(Config::with_enabled_statistics, args.enabled_statistics) + .with_optional_env( + Config::with_custom_statistics_service_url, + args.statistics_service_url, + STATISTICS_SERVICE_DOMAIN_ADDRESS, + ) + .with_validated_optional( + |cfg, wallet_address| cfg.with_wallet_address(wallet_address), + args.wallet_address, + ensure_correct_bech32_prefix, + )? + .with_optional(Config::with_custom_persistent_store, args.datastore) + .with_optional(Config::with_cosmos_mnemonic, args.mnemonic) + .with_optional_custom_env( + Config::with_custom_validator_nyxd, + args.nyxd_urls, + NYXD, + nym_config::parse_urls, + ) + .with_optional( + Config::with_only_coconut_credentials, + args.only_coconut_credentials, ); - } else if std::env::var(CONFIGURED).is_ok() { - if let Some(raw_url) = read_var_if_not_default(STATISTICS_SERVICE_DOMAIN_ADDRESS) { - config = config.with_custom_statistics_service_url( - raw_url - .parse() - .expect("the provided statistics service url is invalid"), - ); - } - } - if let Some(raw_validators) = args.validator_apis { - config = config.with_custom_validator_apis(parse_validators(&raw_validators)); - } else if std::env::var(CONFIGURED).is_ok() { - if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) { - config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators)) - } - } - - if let Some(ref raw_validators) = args.validators { - config = config.with_custom_validator_nymd(parse_validators(raw_validators)); - } else if std::env::var(CONFIGURED).is_ok() { - if let Some(raw_validators) = read_var_if_not_default(NYMD_VALIDATOR) { - config = config.with_custom_validator_nymd(::config::parse_validators(&raw_validators)) - } - } - - if let Some(wallet_address) = args.wallet_address { - let trimmed = wallet_address.trim(); - validate_bech32_address_or_exit(trimmed); - config = config.with_wallet_address(trimmed); - } - - if let Some(datastore_path) = args.datastore { - config = config.with_custom_persistent_store(datastore_path); - } - - if let Some(cosmos_mnemonic) = args.mnemonic { - config = config.with_cosmos_mnemonic( - bip39::Mnemonic::from_str(&cosmos_mnemonic).expect("Provided mnemonic is invalid"), - ); - } - - #[cfg(feature = "coconut")] - { - config = config.with_only_coconut_credentials(args.only_coconut_credentials); - } - - config + Ok(config) } -/// Ensures that a given bech32 address is valid, or exits -pub(crate) fn validate_bech32_address_or_exit(address: &str) { - let prefix = std::env::var(BECH32_PREFIX).expect("bech32 prefix not set"); - if let Err(bech32_address_validation::Bech32Error::DecodeFailed(err)) = - bech32_address_validation::try_bech32_decode(address) - { - let error_message = format!("Error: wallet address decoding failed: {}", err).red(); - println!("{}", error_message); - println!("Exiting..."); - process::exit(1); +/// Ensures that a given bech32 address is valid +pub(crate) fn ensure_correct_bech32_prefix(address: &AccountId) -> Result<(), GatewayError> { + let expected_prefix = std::env::var(BECH32_PREFIX).expect("bech32 prefix not set"); + let actual_prefix = address.prefix(); + if expected_prefix != actual_prefix { + return Err(GatewayError::InvalidBech32AccountPrefix { + account: address.to_owned(), + expected_prefix, + actual_prefix: actual_prefix.to_owned(), + }); } - if let Err(bech32_address_validation::Bech32Error::WrongPrefix(err)) = - bech32_address_validation::validate_bech32_prefix(&prefix, address) - { - let error_message = format!("Error: wallet address type is wrong, {}", err).red(); - println!("{}", error_message); - println!("Exiting..."); - process::exit(1); - } + Ok(()) } // this only checks compatibility between config the binary. It does not take into consideration // network version. It might do so in the future. -pub(crate) fn version_check(cfg: &Config) -> bool { +pub(crate) fn ensure_config_version_compatibility(cfg: &Config) -> Result<(), GatewayError> { let binary_version = env!("CARGO_PKG_VERSION"); let config_version = cfg.get_version(); - if binary_version != config_version { - log::warn!("The gateway binary has different version than what is specified in config file! {} and {}", binary_version, config_version); - if version_checker::is_minor_version_compatible(binary_version, config_version) { - log::info!("but they are still semver compatible. However, consider running the `upgrade` command"); - true - } else { - log::error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again"); - false - } + + if binary_version == config_version { + Ok(()) + } else if version_checker::is_minor_version_compatible(binary_version, config_version) { + log::warn!( + "The gateway binary has different version than what is specified in config file! {binary_version} and {config_version}. \ + But, they are still semver compatible. However, consider running the `upgrade` command."); + Ok(()) } else { - true + log::error!( + "The gateway binary has different version than what is specified in config file! {binary_version} and {config_version}. \ + And they are semver incompatible! - please run the `upgrade` command before attempting `run` again"); + Err(GatewayError::LocalVersionCheckFailure { + binary_version: binary_version.to_owned(), + config_version: config_version.to_owned(), + }) } } diff --git a/gateway/src/commands/node_details.rs b/gateway/src/commands/node_details.rs index 15602113c1..694bdb15b8 100644 --- a/gateway/src/commands/node_details.rs +++ b/gateway/src/commands/node_details.rs @@ -1,10 +1,11 @@ -// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> +// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crate::config::Config; +use crate::commands::OverrideConfig; +use crate::support::config::build_config; +use crate::OutputFormat; use clap::Args; -use config::NymConfig; -use log::error; +use std::error::Error; #[derive(Args, Clone)] pub struct NodeDetails { @@ -13,20 +14,13 @@ pub struct NodeDetails { id: String, } -pub async fn execute(args: &NodeDetails) { - let config = match Config::load_from_file(Some(&args.id)) { - Ok(cfg) => cfg, - Err(err) => { - error!( - "Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", - args.id, - err, - ); - return; - } - }; +pub async fn execute( + args: NodeDetails, + output: OutputFormat, +) -> Result<(), Box<dyn Error + Send + Sync>> { + let config = build_config(args.id.clone(), OverrideConfig::default())?; - crate::node::create_gateway(config) + Ok(crate::node::create_gateway(config) .await - .print_node_details(); + .print_node_details(output)?) } diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index 37320877fe..4000fd8a7f 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -1,13 +1,16 @@ -// Copyright 2020 - Nym Technologies SA <contact@nymtech.net> +// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 +use crate::support::config::build_config; use crate::{ - commands::{override_config, version_check, OverrideConfig}, - config::Config, + commands::{ensure_config_version_compatibility, OverrideConfig}, + OutputFormat, }; use clap::Args; -use config::NymConfig; -use log::*; +use std::error::Error; +use std::net::IpAddr; +use std::path::PathBuf; +use validator_client::nyxd; #[derive(Args, Clone)] pub struct Run { @@ -17,11 +20,11 @@ pub struct Run { /// The custom host on which the gateway will be running for receiving sphinx packets #[clap(long)] - host: Option<String>, + host: Option<IpAddr>, /// The wallet address you will use to bond this gateway, e.g. nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9 #[clap(long)] - wallet_address: Option<String>, + wallet_address: Option<nyxd::AccountId>, /// The port on which the gateway will be listening for sphinx packets #[clap(long)] @@ -33,29 +36,37 @@ pub struct Run { /// The host that will be reported to the directory server #[clap(long)] + // TODO: could this be changed to `Option<url::Url>`? announce_host: Option<String>, /// Path to sqlite database containing all gateway persistent data #[clap(long)] - datastore: Option<String>, + datastore: Option<PathBuf>, - /// Comma separated list of endpoints of the validators APIs - #[clap(long)] - validator_apis: Option<String>, + /// Comma separated list of endpoints of nym APIs + #[clap(long, alias = "validator_apis", value_delimiter = ',')] + // the alias here is included for backwards compatibility (1.1.4 and before) + nym_apis: Option<Vec<url::Url>>, /// Comma separated list of endpoints of the validator - #[clap(long)] - validators: Option<String>, + #[clap( + long, + alias = "validators", + alias = "nyxd_validators", + value_delimiter = ',', + hide = true + )] + // the alias here is included for backwards compatibility (1.1.4 and before) + nyxd_urls: Option<Vec<url::Url>>, /// Cosmos wallet mnemonic #[clap(long)] - mnemonic: Option<String>, + mnemonic: Option<bip39::Mnemonic>, /// Set this gateway to work only with coconut credentials; that would disallow clients to /// bypass bandwidth credential requirement - #[cfg(feature = "coconut")] - #[clap(long)] - only_coconut_credentials: bool, + #[clap(long, hide = true)] + only_coconut_credentials: Option<bool>, /// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server #[clap(long)] @@ -63,7 +74,7 @@ pub struct Run { /// URL where a statistics aggregator is running. The default value is a Nym aggregator server #[clap(long)] - statistics_service_url: Option<String>, + statistics_service_url: Option<url::Url>, } impl From<Run> for OverrideConfig { @@ -75,65 +86,48 @@ impl From<Run> for OverrideConfig { clients_port: run_config.clients_port, datastore: run_config.datastore, announce_host: run_config.announce_host, - validator_apis: run_config.validator_apis, - validators: run_config.validators, + nym_apis: run_config.nym_apis, mnemonic: run_config.mnemonic, - #[cfg(feature = "coconut")] - only_coconut_credentials: run_config.only_coconut_credentials, - enabled_statistics: run_config.enabled_statistics, statistics_service_url: run_config.statistics_service_url, + nyxd_urls: run_config.nyxd_urls, + only_coconut_credentials: run_config.only_coconut_credentials, } } } fn show_binding_warning(address: String) { - println!("\n##### NOTE #####"); - println!( + eprintln!("\n##### NOTE #####"); + eprintln!( "\nYou are trying to bind to {} - you might not be accessible to other nodes\n\ You can ignore this warning if you're running setup on a local network \n\ or have set a custom 'announce-host'", address ); - println!("\n\n"); + eprintln!("\n\n"); } fn special_addresses() -> Vec<&'static str> { vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"] } -pub async fn execute(args: &Run) { - println!("Starting gateway {}...", args.id); +pub async fn execute(args: Run, output: OutputFormat) -> Result<(), Box<dyn Error + Send + Sync>> { + let id = args.id.clone(); + println!("Starting gateway {id}..."); - let mut config = match Config::load_from_file(Some(&args.id)) { - Ok(cfg) => cfg, - Err(err) => { - error!( - "Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", - args.id, - err, - ); - return; - } - }; - - config = override_config(config, OverrideConfig::from(args.clone())); - - if !version_check(&config) { - error!("failed the local version check"); - return; - } + let config = build_config(id, args)?; + ensure_config_version_compatibility(&config)?; if special_addresses().contains(&&*config.get_listening_address().to_string()) { show_binding_warning(config.get_listening_address().to_string()); } let mut gateway = crate::node::create_gateway(config).await; - println!( + eprintln!( "\nTo bond your gateway you will need to install the Nym wallet, go to https://nymtech.net/get-involved and select the Download button.\n\ Select the correct version and install it to your machine. You will need to provide the following: \n "); - gateway.print_node_details(); + gateway.print_node_details(output)?; - gateway.run().await; + gateway.run().await } diff --git a/gateway/src/commands/sign.rs b/gateway/src/commands/sign.rs index 17d6eebfd4..61b587fa21 100644 --- a/gateway/src/commands/sign.rs +++ b/gateway/src/commands/sign.rs @@ -1,18 +1,21 @@ -// Copyright 2020 - Nym Technologies SA <contact@nymtech.net> +// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 +use crate::commands::{ensure_correct_bech32_prefix, OverrideConfig}; +use crate::error::GatewayError; +use crate::support::config::build_config; use crate::{ - commands::{validate_bech32_address_or_exit, version_check}, - config::{persistence::pathfinder::GatewayPathfinder, Config}, + commands::ensure_config_version_compatibility, + config::persistence::pathfinder::GatewayPathfinder, }; use anyhow::{anyhow, Result}; use clap::{ArgGroup, Args}; -use config::NymConfig; -use crypto::asymmetric::identity; -use log::error; +use nym_crypto::asymmetric::identity; +use std::error::Error; +use validator_client::nyxd; #[derive(Args, Clone)] -#[clap(group(ArgGroup::new("sign").required(true).args(&["address", "text"])))] +#[clap(group(ArgGroup::new("sign").required(true).args(&["wallet_address", "text"])))] pub struct Sign { /// The id of the mixnode you want to sign with #[clap(long)] @@ -20,7 +23,7 @@ pub struct Sign { /// Signs your blockchain address with your identity key #[clap(long)] - address: Option<String>, + wallet_address: Option<nyxd::AccountId>, /// Signs an arbitrary piece of text with your identity key #[clap(long)] @@ -29,7 +32,7 @@ pub struct Sign { enum SignedTarget { Text(String), - Address(String), + Address(nyxd::AccountId), } impl TryFrom<Sign> for SignedTarget { @@ -38,7 +41,7 @@ impl TryFrom<Sign> for SignedTarget { fn try_from(args: Sign) -> Result<Self, Self::Error> { if let Some(text) = args.text { Ok(SignedTarget::Text(text)) - } else if let Some(address) = args.address { + } else if let Some(address) = args.wallet_address { Ok(SignedTarget::Address(address)) } else { // This is unreachable, and hopefully clap will support it explicitly by outputting an @@ -50,69 +53,53 @@ impl TryFrom<Sign> for SignedTarget { } pub fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair { - let identity_keypair: identity::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new( - pathfinder.private_identity_key().to_owned(), - pathfinder.public_identity_key().to_owned(), - )) - .expect("Failed to read stored identity key files"); + let identity_keypair: identity::KeyPair = + nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( + pathfinder.private_identity_key().to_owned(), + pathfinder.public_identity_key().to_owned(), + )) + .expect("Failed to read stored identity key files"); identity_keypair } -fn print_signed_address(private_key: &identity::PrivateKey, raw_address: &str) { - let trimmed = raw_address.trim(); - validate_bech32_address_or_exit(trimmed); - let signature = private_key.sign_text(trimmed); +fn print_signed_address( + private_key: &identity::PrivateKey, + wallet_address: nyxd::AccountId, +) -> Result<(), GatewayError> { + // perform extra validation to ensure we have correct prefix + ensure_correct_bech32_prefix(&wallet_address)?; - println!( - "The base58-encoded signature on '{}' is: {}", - trimmed, signature - ); + let signature = private_key.sign_text(wallet_address.as_ref()); + eprintln!("The base58-encoded signature on '{wallet_address}' is: {signature}"); + Ok(()) } fn print_signed_text(private_key: &identity::PrivateKey, text: &str) { - println!( + eprintln!( "Signing the text {:?} using your mixnode's Ed25519 identity key...", text ); let signature = private_key.sign_text(text); - println!( + eprintln!( "The base58-encoded signature on '{}' is: {}", text, signature ); } -pub fn execute(args: &Sign) { - let config = match Config::load_from_file(Some(&args.id)) { - Ok(cfg) => cfg, - Err(err) => { - error!( - "Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", - args.id, - err - ); - return; - } - }; +pub fn execute(args: Sign) -> Result<(), Box<dyn Error + Send + Sync>> { + let config = build_config(args.id.clone(), OverrideConfig::default())?; + ensure_config_version_compatibility(&config)?; - if !version_check(&config) { - error!("failed the local version check"); - return; - } - - let signed_target = match SignedTarget::try_from(args.clone()) { - Ok(s) => s, - Err(err) => { - error!("{}", err); - return; - } - }; + let signed_target = SignedTarget::try_from(args)?; let pathfinder = GatewayPathfinder::new_from_config(&config); let identity_keypair = load_identity_keys(&pathfinder); match signed_target { SignedTarget::Text(text) => print_signed_text(identity_keypair.private_key(), &text), - SignedTarget::Address(addr) => print_signed_address(identity_keypair.private_key(), &addr), + SignedTarget::Address(addr) => print_signed_address(identity_keypair.private_key(), addr)?, } + + Ok(()) } diff --git a/gateway/src/commands/upgrade.rs b/gateway/src/commands/upgrade.rs index 7d8e5481a6..e3f7e10bdf 100644 --- a/gateway/src/commands/upgrade.rs +++ b/gateway/src/commands/upgrade.rs @@ -3,10 +3,10 @@ use crate::config::{Config, MISSING_VALUE}; use clap::Args; -use config::NymConfig; +use nym_bin_common::version_checker::Version; +use nym_config::NymConfig; use std::fmt::Display; use std::process; -use version_checker::Version; #[derive(Args, Clone)] pub struct Upgrade { @@ -22,7 +22,7 @@ fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! } fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) { - println!( + eprintln!( "\n==================\nTrying to upgrade gateway from {} to {} ...", from, to ); @@ -36,7 +36,7 @@ fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) { } fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) { - println!( + eprintln!( "Upgrade from {} to {} was successful!\n==================\n", from, to ); @@ -57,7 +57,7 @@ fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! fn parse_config_version(config: &Config) -> Version { let version = Version::parse(config.get_version()).unwrap_or_else(|err| { - eprintln!("failed to parse client version! - {:?}", err); + eprintln!("failed to parse client version! - {err}"); process::exit(1) }); @@ -103,10 +103,10 @@ fn minor_0_12_upgrade( print_start_upgrade(config_version, &to_version); - let upgraded_config = config.with_custom_version(to_version.to_string().as_ref()); + let upgraded_config = config.with_custom_version(to_version.to_string()); upgraded_config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); + eprintln!("failed to overwrite config file! - {err}"); print_failed_upgrade(config_version, &to_version); process::exit(1); }); @@ -121,7 +121,7 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) { let config_version = parse_config_version(&config); if config_version == package_version { - println!("You're using the most recent version!"); + eprintln!("You're using the most recent version!"); return; } @@ -139,8 +139,8 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) { pub async fn execute(args: &Upgrade) { let package_version = parse_package_version(); - let existing_config = Config::load_from_file(Some(&args.id)).unwrap_or_else(|err| { - eprintln!("failed to load existing config file! - {:?}", err); + let existing_config = Config::load_from_file(&args.id).unwrap_or_else(|err| { + eprintln!("failed to load existing config file! - {err}"); process::exit(1) }); diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index ac4f734dad..2442632d59 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -2,16 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::template::config_template; -use config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT}; -use config::NymConfig; -use log::error; -use network_defaults::mainnet::{API_VALIDATOR, NYMD_VALIDATOR, STATISTICS_SERVICE_DOMAIN_ADDRESS}; +use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT}; +use nym_config::NymConfig; +use nym_network_defaults::mainnet::{NYM_API, NYXD_URL, STATISTICS_SERVICE_DOMAIN_ADDRESS}; use serde::{Deserialize, Serialize}; use std::net::IpAddr; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; use url::Url; +use validator_client::nyxd; pub mod persistence; mod template; @@ -128,7 +128,6 @@ impl Config { self } - #[cfg(feature = "coconut")] pub fn with_only_coconut_credentials(mut self, only_coconut_credentials: bool) -> Self { self.gateway.only_coconut_credentials = only_coconut_credentials; self @@ -144,13 +143,13 @@ impl Config { self } - pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec<Url>) -> Self { - self.gateway.validator_api_urls = validator_api_urls; + pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec<Url>) -> Self { + self.gateway.nym_api_urls = nym_api_urls; self } - pub fn with_custom_validator_nymd(mut self, validator_nymd_urls: Vec<Url>) -> Self { - self.gateway.validator_nymd_urls = validator_nymd_urls; + pub fn with_custom_validator_nyxd(mut self, validator_nyxd_urls: Vec<Url>) -> Self { + self.gateway.nyxd_urls = validator_nyxd_urls; self } @@ -159,16 +158,8 @@ impl Config { self } - pub fn with_listening_address<S: Into<String>>(mut self, listening_address: S) -> Self { - let listening_address_string = listening_address.into(); - if let Ok(ip_addr) = listening_address_string.parse() { - self.gateway.listening_address = ip_addr; - } else { - error!( - "failed to change listening address. the provided value ({}) was invalid", - listening_address_string - ); - } + pub fn with_listening_address(mut self, listening_address: IpAddr) -> Self { + self.gateway.listening_address = listening_address; self } @@ -192,18 +183,18 @@ impl Config { self } - pub fn with_custom_persistent_store<S: Into<String>>(mut self, store_dir: S) -> Self { - self.gateway.persistent_storage = PathBuf::from(store_dir.into()); + pub fn with_custom_persistent_store(mut self, store_dir: PathBuf) -> Self { + self.gateway.persistent_storage = store_dir; self } - pub fn with_custom_version(mut self, version: &str) -> Self { - self.gateway.version = version.to_string(); + pub fn with_custom_version<S: Into<String>>(mut self, version: S) -> Self { + self.gateway.version = version.into(); self } - pub fn with_wallet_address(mut self, wallet_address: &str) -> Self { - self.gateway.wallet_address = wallet_address.to_string(); + pub fn with_wallet_address(mut self, wallet_address: nyxd::AccountId) -> Self { + self.gateway.wallet_address = Some(wallet_address); self } @@ -240,16 +231,14 @@ impl Config { self.gateway.statistics_service_url.clone() } - pub fn get_validator_api_endpoints(&self) -> Vec<Url> { - self.gateway.validator_api_urls.clone() + pub fn get_nym_api_endpoints(&self) -> Vec<Url> { + self.gateway.nym_api_urls.clone() } - #[cfg(feature = "coconut")] - pub fn get_validator_nymd_endpoints(&self) -> Vec<Url> { - self.gateway.validator_nymd_urls.clone() + pub fn get_nyxd_urls(&self) -> Vec<Url> { + self.gateway.nyxd_urls.clone() } - #[cfg(feature = "coconut")] pub fn get_cosmos_mnemonic(&self) -> bip39::Mnemonic { self.gateway.cosmos_mnemonic.clone() } @@ -302,8 +291,8 @@ impl Config { &self.gateway.version } - pub fn get_wallet_address(&self) -> &str { - &self.gateway.wallet_address + pub fn get_wallet_address(&self) -> Option<nyxd::AccountId> { + self.gateway.wallet_address.clone() } } @@ -353,17 +342,19 @@ pub struct Gateway { /// Path to file containing public sphinx key. public_sphinx_key_file: PathBuf, - /// Wheather gateway collects and sends anonymized statistics + /// Whether gateway collects and sends anonymized statistics enabled_statistics: bool, /// Domain address of the statistics service statistics_service_url: Url, - /// Addresses to APIs running on validator from which the node gets the view of the network. - validator_api_urls: Vec<Url>, + /// Addresses to APIs from which the node gets the view of the network. + #[serde(alias = "validator_api_urls")] + nym_api_urls: Vec<Url>, /// Addresses to validators which the node uses to check for double spending of ERC20 tokens. - validator_nymd_urls: Vec<Url>, + #[serde(alias = "validator_nymd_urls")] + nyxd_urls: Vec<Url>, /// Mnemonic of a cosmos wallet used in checking for double spending. cosmos_mnemonic: bip39::Mnemonic, @@ -377,28 +368,29 @@ pub struct Gateway { persistent_storage: PathBuf, /// The Cosmos wallet address that will control this gateway - wallet_address: String, + // the only reason this is an Option is because of the lack of existence of a sane default value + wallet_address: Option<nyxd::AccountId>, } impl Gateway { fn default_private_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_sphinx.pem") + Config::default_data_directory(id).join("private_sphinx.pem") } fn default_public_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_sphinx.pem") + Config::default_data_directory(id).join("public_sphinx.pem") } fn default_private_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_identity.pem") + Config::default_data_directory(id).join("private_identity.pem") } fn default_public_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_identity.pem") + Config::default_data_directory(id).join("public_identity.pem") } fn default_database_path(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("db.sqlite") + Config::default_data_directory(id).join("db.sqlite") } } @@ -417,13 +409,14 @@ impl Default for Gateway { private_sphinx_key_file: Default::default(), public_sphinx_key_file: Default::default(), enabled_statistics: false, - statistics_service_url: Url::from_str(STATISTICS_SERVICE_DOMAIN_ADDRESS).expect("Invalid default statistics service URL"), - validator_api_urls: vec![Url::from_str(API_VALIDATOR).expect("Invalid default API URL")], - validator_nymd_urls: vec![Url::from_str(NYMD_VALIDATOR).expect("Invalid default nymd URL")], - cosmos_mnemonic: bip39::Mnemonic::from_str("exact antique hybrid width raise anchor puzzle degree fee quit long crack net vague hip despair write put useless civil mechanic broom music day").unwrap(), + statistics_service_url: Url::from_str(STATISTICS_SERVICE_DOMAIN_ADDRESS) + .expect("Invalid default statistics service URL"), + nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")], + nyxd_urls: vec![Url::from_str(NYXD_URL).expect("Invalid default nyxd URL")], + cosmos_mnemonic: bip39::Mnemonic::generate(24).unwrap(), nym_root_directory: Config::default_root_directory(), persistent_storage: Default::default(), - wallet_address: "nymXXXXXXXX".to_string(), + wallet_address: None, } } } diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index faba004a50..2690c38385 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -60,15 +60,15 @@ enabled_statistics = {{ gateway.enabled_statistics }} statistics_service_url = '{{ gateway.statistics_service_url }}' # Addresses to APIs running on validator from which the node gets the view of the network. -validator_api_urls = [ - {{#each gateway.validator_api_urls }} +nym_api_urls = [ + {{#each gateway.nym_api_urls }} '{{this}}', {{/each}} ] -# Addresses to validators which the node uses to check for double spending of ERC20 tokens. -validator_nymd_urls = [ - {{#each gateway.validator_nymd_urls }} +# Addresses to validators which the node uses to check for double spending of nym tokens. +nyxd_urls = [ + {{#each gateway.nyxd_urls }} '{{this}}', {{/each}} ] diff --git a/gateway/src/error.rs b/gateway/src/error.rs new file mode 100644 index 0000000000..4692686cb9 --- /dev/null +++ b/gateway/src/error.rs @@ -0,0 +1,47 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use std::io; +use std::path::PathBuf; +use thiserror::Error; +use validator_client::nyxd::AccountId; +use validator_client::ValidatorClientError; + +#[derive(Debug, Error)] +pub(crate) enum GatewayError { + #[error( + "failed to load config file for id {id} using path {path}. detailed message: {source}" + )] + ConfigLoadFailure { + id: String, + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("the configured version of the gateway ({config_version}) is incompatible with the binary version ({binary_version})")] + LocalVersionCheckFailure { + binary_version: String, + config_version: String, + }, + + #[error("another node on the network seems to have identical announce-host ({host})! Their identity is {remote_identity}, while ours is {local_identity}")] + DuplicateNodeHost { + host: String, + local_identity: String, + remote_identity: String, + }, + + #[error("could not obtain the information about current gateways on the network: {source}")] + NetworkGatewaysQueryFailure { + #[source] + source: ValidatorClientError, + }, + + #[error("address {account} has an invalid bech32 prefix. it uses '{actual_prefix}' while '{expected_prefix}' was expected")] + InvalidBech32AccountPrefix { + account: AccountId, + expected_prefix: String, + actual_prefix: String, + }, +} diff --git a/gateway/src/main.rs b/gateway/src/main.rs index bfcfd0e563..11f8e69457 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -1,92 +1,85 @@ // Copyright 2020 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use clap::{crate_version, Parser}; -use logging::setup_logging; -use network_defaults::setup_env; -use once_cell::sync::OnceCell; +use clap::{crate_name, crate_version, Parser, ValueEnum}; +use colored::Colorize; +use lazy_static::lazy_static; +use log::error; +use nym_bin_common::logging::setup_logging; +use nym_bin_common::{build_information::BinaryBuildInformation, logging::banner}; +use nym_network_defaults::setup_env; +use std::error::Error; mod commands; mod config; +pub(crate) mod error; mod node; +pub(crate) mod support; -static LONG_VERSION: OnceCell<String> = OnceCell::new(); +lazy_static! { + pub static ref PRETTY_BUILD_INFORMATION: String = + BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print(); +} -// Helper for passing LONG_ABOUT to clap -fn long_version_static() -> &'static str { - LONG_VERSION.get().expect("Failed to get long about text") +// Helper for passing LONG_VERSION to clap +fn pretty_build_info_static() -> &'static str { + &PRETTY_BUILD_INFORMATION +} + +#[derive(Clone, ValueEnum)] +pub enum OutputFormat { + Json, + Text, +} + +impl Default for OutputFormat { + fn default() -> Self { + OutputFormat::Text + } } #[derive(Parser)] -#[clap(author = "Nymtech", version, about, long_version = long_version_static())] +#[clap(author = "Nymtech", version, about, long_version = pretty_build_info_static())] struct Cli { /// Path pointing to an env file that configures the gateway. #[clap(short, long)] pub(crate) config_env_file: Option<std::path::PathBuf>, + #[clap(short, long)] + pub(crate) output: Option<OutputFormat>, + #[clap(subcommand)] command: commands::Commands, } +impl Cli { + fn output(&self) -> OutputFormat { + if let Some(ref output) = self.output { + output.clone() + } else { + OutputFormat::default() + } + } +} + #[tokio::main] -async fn main() { +async fn main() -> Result<(), Box<dyn Error + Send + Sync>> { setup_logging(); - println!("{}", banner()); - LONG_VERSION - .set(long_version()) - .expect("Failed to set long about text"); + if atty::is(atty::Stream::Stdout) { + println!("{}", banner(crate_name!(), crate_version!())); + } let args = Cli::parse(); - setup_env(args.config_env_file.clone()); - commands::execute(args).await; -} + setup_env(args.config_env_file.as_ref()); -fn banner() -> String { - format!( - r#" - - _ __ _ _ _ __ ___ - | '_ \| | | | '_ \ _ \ - | | | | |_| | | | | | | - |_| |_|\__, |_| |_| |_| - |___/ - - (gateway - version {:}) - - "#, - crate_version!() - ) -} - -fn long_version() -> String { - format!( - r#" -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -"#, - "Build Timestamp:", - env!("VERGEN_BUILD_TIMESTAMP"), - "Build Version:", - env!("VERGEN_BUILD_SEMVER"), - "Commit SHA:", - env!("VERGEN_GIT_SHA"), - "Commit Date:", - env!("VERGEN_GIT_COMMIT_TIMESTAMP"), - "Commit Branch:", - env!("VERGEN_GIT_BRANCH"), - "rustc Version:", - env!("VERGEN_RUSTC_SEMVER"), - "rustc Channel:", - env!("VERGEN_RUSTC_CHANNEL"), - "cargo Profile:", - env!("VERGEN_CARGO_PROFILE") - ) + commands::execute(args).await.map_err(|err| { + if atty::is(atty::Stream::Stdout) { + let error_message = format!("{err}").red(); + error!("{error_message}"); + error!("Exiting..."); + } + err + }) } #[cfg(test)] @@ -96,9 +89,6 @@ mod tests { #[test] fn verify_cli() { - LONG_VERSION - .set(long_version()) - .expect("Failed to set long about text"); Cli::command().debug_assert(); } } diff --git a/gateway/src/node/client_handling/active_clients.rs b/gateway/src/node/client_handling/active_clients.rs index 7eeefd2a65..d2de2cc5b5 100644 --- a/gateway/src/node/client_handling/active_clients.rs +++ b/gateway/src/node/client_handling/active_clients.rs @@ -3,7 +3,7 @@ use crate::node::client_handling::websocket::message_receiver::MixMessageSender; use dashmap::DashMap; -use nymsphinx::DestinationAddressBytes; +use nym_sphinx::DestinationAddressBytes; use std::sync::Arc; #[derive(Clone)] diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index e88b531f83..99f1cf095b 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -1,10 +1,7 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "coconut")] -use coconut_interface::Credential; -#[cfg(not(feature = "coconut"))] -use credentials::token::bandwidth::TokenCredential; +use nym_coconut_interface::Credential; pub struct Bandwidth { value: u64, @@ -16,22 +13,12 @@ impl Bandwidth { } } -#[cfg(feature = "coconut")] impl From<Credential> for Bandwidth { fn from(credential: Credential) -> Self { let token_value = credential.voucher_value(); - let bandwidth_bytes = token_value * network_defaults::BYTES_PER_UTOKEN; + let bandwidth_bytes = token_value * nym_network_defaults::BYTES_PER_UTOKEN; Bandwidth { value: bandwidth_bytes, } } } - -#[cfg(not(feature = "coconut"))] -impl From<TokenCredential> for Bandwidth { - fn from(credential: TokenCredential) -> Self { - Bandwidth { - value: credential.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 cb2d85b5a9..7ff47a9f11 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -10,7 +10,7 @@ use gateway_requests::iv::IVConversionError; use gateway_requests::types::{BinaryRequest, ServerResponse}; use gateway_requests::{ClientControlRequest, GatewayRequestsError}; use log::*; -use nymsphinx::forwarding::packet::MixPacket; +use nym_sphinx::forwarding::packet::MixPacket; use rand::{CryptoRng, Rng}; use std::convert::TryFrom; use std::process; @@ -21,6 +21,7 @@ use tokio_tungstenite::tungstenite::protocol::Message; use crate::node::client_handling::bandwidth::Bandwidth; use crate::node::client_handling::FREE_TESTNET_BANDWIDTH_VALUE; use gateway_requests::iv::IV; +use nym_task::TaskClient; #[derive(Debug, Error)] pub(crate) enum RequestHandlingError { @@ -48,24 +49,23 @@ pub(crate) enum RequestHandlingError { #[error("This gateway is only accepting coconut credentials for bandwidth")] OnlyCoconutCredentials, - #[error("Nymd Error - {0}")] - NymdError(#[from] validator_client::nymd::error::NymdError), + #[error("Nyxd Error - {0}")] + NyxdError(#[from] validator_client::nyxd::error::NyxdError), - #[cfg(feature = "coconut")] #[error("Validator API error - {0}")] APIError(#[from] validator_client::ValidatorClientError), - #[cfg(feature = "coconut")] - #[error("Not enough validator API endpoints provided. Needed {needed}, received {received}")] - NotEnoughValidatorAPIs { received: usize, needed: usize }, + #[error("Not enough nym API endpoints provided. Needed {needed}, received {received}")] + NotEnoughNymAPIs { received: usize, needed: usize }, - #[cfg(feature = "coconut")] #[error("There was a problem with the proposal id: {reason}")] ProposalIdError { reason: String }, - #[cfg(feature = "coconut")] #[error("Coconut interface error - {0}")] - CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), + CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), + + #[error("Credential error - {0}")] + CredentialError(#[from] nym_credentials::error::Error), } impl RequestHandlingError { @@ -182,12 +182,11 @@ where /// * `mix_packet`: packet received from the client that should get forwarded into the network. fn forward_packet(&self, mix_packet: MixPacket) { if let Err(err) = self.inner.outbound_mix_sender.unbounded_send(mix_packet) { - error!("We failed to forward requested mix packet - {}. Presumably our mix forwarder has crashed. We cannot continue.", err); + error!("We failed to forward requested mix packet - {err}. Presumably our mix forwarder has crashed. We cannot continue."); process::exit(1); } } - #[cfg(feature = "coconut")] /// Tries to handle the received bandwidth request by checking correctness of the received data /// and if successful, increases client's bandwidth by an appropriate amount. /// @@ -195,7 +194,7 @@ where /// /// * `enc_credential`: raw encrypted bandwidth credential to verify. /// * `iv`: fresh iv used for the credential. - async fn handle_coconut_bandwidth( + async fn handle_bandwidth( &mut self, enc_credential: Vec<u8>, iv: Vec<u8>, @@ -207,12 +206,28 @@ where iv, )?; - if !credential.verify( - self.inner - .coconut_verifier - .as_ref() - .aggregated_verification_key(), - ) { + // Get the latest coconut signers and their VK + let credential_api_clients = self + .inner + .coconut_verifier + .all_coconut_api_clients(*credential.epoch_id()) + .await?; + let current_api_clients = self + .inner + .coconut_verifier + .all_current_coconut_api_clients() + .await?; + if credential_api_clients.is_empty() || current_api_clients.is_empty() { + return Err(RequestHandlingError::NotEnoughNymAPIs { + received: 0, + needed: 1, + }); + } + + let aggregated_verification_key = + nym_credentials::obtain_aggregate_verification_key(&credential_api_clients).await?; + + if !credential.verify(&aggregated_verification_key) { return Err(RequestHandlingError::InvalidBandwidthCredential( String::from("credential failed to verify on gateway"), )); @@ -220,7 +235,7 @@ where self.inner .coconut_verifier - .release_funds(&credential) + .release_funds(current_api_clients, &credential) .await?; let bandwidth = Bandwidth::from(credential); @@ -242,70 +257,6 @@ where Ok(ServerResponse::Bandwidth { available_total }) } - #[cfg(not(feature = "coconut"))] - /// Tries to handle the received bandwidth request by checking correctness of the received data - /// and if successful, increases client's bandwidth by an appropriate amount. - /// - /// # Arguments - /// - /// * `enc_credential`: raw encrypted bandwidth credential to verify. - /// * `iv`: fresh iv used for the credential. - async fn handle_token_bandwidth( - &mut self, - enc_credential: Vec<u8>, - iv: Vec<u8>, - ) -> Result<ServerResponse, RequestHandlingError> { - let iv = IV::try_from_bytes(&iv)?; - let credential = ClientControlRequest::try_from_enc_token_bandwidth_credential( - enc_credential, - &self.client.shared_keys, - iv, - )?; - if !self - .inner - .check_local_identity(&credential.gateway_identity()) - { - return Err(RequestHandlingError::InvalidBandwidthCredential( - String::from("gateway"), - )); - } - - if !credential.verify_signature() { - return Err(RequestHandlingError::InvalidBandwidthCredential( - String::from("gateway"), - )); - } - - let bandwidth = Bandwidth::from(credential); - let bandwidth_value = bandwidth.value(); - - if bandwidth_value > i64::MAX as u64 { - // note that this would have represented more than 1 exabyte, - // which is like 125,000 worth of hard drives so I don't think we have - // to worry about it for now... - warn!("Somehow we received bandwidth value higher than 9223372036854775807. We don't really want to deal with this now"); - return Err(RequestHandlingError::UnsupportedBandwidthValue( - bandwidth_value, - )); - } - - self.increase_bandwidth(bandwidth_value as i64).await?; - let available_total = self.get_available_bandwidth().await?; - - Ok(ServerResponse::Bandwidth { available_total }) - } - - async fn handle_bandwidth( - &mut self, - enc_credential: Vec<u8>, - iv: Vec<u8>, - ) -> Result<ServerResponse, RequestHandlingError> { - #[cfg(feature = "coconut")] - return self.handle_coconut_bandwidth(enc_credential, iv).await; - #[cfg(not(feature = "coconut"))] - return self.handle_token_bandwidth(enc_credential, iv).await; - } - async fn handle_claim_testnet_bandwidth( &mut self, ) -> Result<ServerResponse, RequestHandlingError> { @@ -412,21 +363,24 @@ where /// Simultaneously listens for incoming client requests, which realistically should only be /// binary requests to forward sphinx packets or increase bandwidth /// and for sphinx packets received from the mix network that should be sent back to the client. - pub(crate) async fn listen_for_requests(mut self) + pub(crate) async fn listen_for_requests(mut self, mut shutdown: TaskClient) where S: AsyncRead + AsyncWrite + Unpin, St: Storage, { trace!("Started listening for ALL incoming requests..."); - loop { + while !shutdown.is_shutdown() { tokio::select! { + _ = shutdown.recv() => { + log::trace!("client_handling::AuthenticatedHandler: received shutdown"); + } socket_msg = self.inner.read_websocket_message() => { let socket_msg = match socket_msg { None => break, Some(Ok(socket_msg)) => socket_msg, Some(Err(err)) => { - error!("failed to obtain message from websocket stream! stopping connection handler: {}", err); + error!("failed to obtain message from websocket stream! stopping connection handler: {err}"); break; } }; @@ -438,8 +392,7 @@ where if let Some(response) = self.handle_request(socket_msg).await { if let Err(err) = self.inner.send_websocket_message(response).await { warn!( - "Failed to send message over websocket: {}. Assuming the connection is dead.", - err + "Failed to send message over websocket: {err}. Assuming the connection is dead.", ); break; } @@ -447,8 +400,8 @@ where }, mix_messages = self.mix_receiver.next() => { let mix_messages = mix_messages.expect("sender was unexpectedly closed! this shouldn't have ever happened!"); - if let Err(e) = self.inner.push_packets_to_client(self.client.shared_keys, mix_messages).await { - warn!("failed to send the unwrapped sphinx packets back to the client - {:?}, assuming the connection is dead", e); + if let Err(err) = self.inner.push_packets_to_client(self.client.shared_keys, mix_messages).await { + warn!("failed to send the unwrapped sphinx packets back to the client - {err}, assuming the connection is dead"); break; } } 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 e83c23e5e9..8c50f93c36 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -1,16 +1,15 @@ // Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use std::time::{Duration, SystemTime}; - use log::*; - -use coconut_interface::{Credential, VerificationKey}; +use nym_coconut_interface::Credential; +use std::time::{Duration, SystemTime}; +use validator_client::nyxd::traits::DkgQueryClient; use validator_client::{ - nymd::{ + nyxd::{ cosmwasm_client::logs::{find_attribute, BANDWIDTH_PROPOSAL_ID}, traits::{CoconutBandwidthSigningClient, MultisigQueryClient, MultisigSigningClient}, - Coin, Fee, SigningNymdClient, + Coin, Fee, SigningNyxdClient, }, Client, CoconutApiClient, }; @@ -21,52 +20,58 @@ const ONE_HOUR_SEC: u64 = 3600; const MAX_FEEGRANT_UNYM: u128 = 10000; pub(crate) struct CoconutVerifier { - api_clients: Vec<CoconutApiClient>, - nymd_client: Client<SigningNymdClient>, + nyxd_client: Client<SigningNyxdClient>, mix_denom_base: String, - aggregated_verification_key: VerificationKey, } impl CoconutVerifier { - pub fn new( - api_clients: Vec<CoconutApiClient>, - nymd_client: Client<SigningNymdClient>, - mix_denom_base: String, - aggregated_verification_key: VerificationKey, - ) -> Result<Self, RequestHandlingError> { - if api_clients.is_empty() { - return Err(RequestHandlingError::NotEnoughValidatorAPIs { - received: 0, - needed: 1, - }); - } - Ok(CoconutVerifier { - api_clients, - nymd_client, + pub fn new(nyxd_client: Client<SigningNyxdClient>) -> Self { + let mix_denom_base = nyxd_client + .nyxd + .current_chain_details() + .mix_denom + .base + .clone(); + + CoconutVerifier { + nyxd_client, mix_denom_base, - aggregated_verification_key, - }) + } } - pub fn aggregated_verification_key(&self) -> &VerificationKey { - &self.aggregated_verification_key + pub async fn all_current_coconut_api_clients( + &self, + ) -> Result<Vec<CoconutApiClient>, RequestHandlingError> { + let epoch_id = self.nyxd_client.nyxd.get_current_epoch().await?.epoch_id; + self.all_coconut_api_clients(epoch_id).await } - pub async fn release_funds(&self, credential: &Credential) -> Result<(), RequestHandlingError> { + pub async fn all_coconut_api_clients( + &self, + epoch_id: u64, + ) -> Result<Vec<CoconutApiClient>, RequestHandlingError> { + Ok(CoconutApiClient::all_coconut_api_clients(&self.nyxd_client, epoch_id).await?) + } + + pub async fn release_funds( + &self, + api_clients: Vec<CoconutApiClient>, + credential: &Credential, + ) -> Result<(), RequestHandlingError> { // Use a custom multiplier for revoke, as the default one (1.3) // isn't enough let revoke_fee = Some(Fee::Auto(Some(1.5))); let res = self - .nymd_client - .nymd + .nyxd_client + .nyxd .spend_credential( Coin::new( credential.voucher_value().into(), self.mix_denom_base.clone(), ), credential.blinded_serial_number(), - self.nymd_client.nymd.address().to_string(), + self.nyxd_client.nyxd.address().to_string(), None, ) .await?; @@ -80,21 +85,21 @@ impl CoconutVerifier { reason: String::from("proposal id could not be parsed to u64"), })?; - let proposal = self.nymd_client.nymd.get_proposal(proposal_id).await?; + let proposal = self.nyxd_client.nyxd.get_proposal(proposal_id).await?; if !credential.has_blinded_serial_number(&proposal.description)? { return Err(RequestHandlingError::ProposalIdError { reason: String::from("proposal has different serial number"), }); } - let req = validator_api_requests::coconut::VerifyCredentialBody::new( + let req = nym_api_requests::coconut::VerifyCredentialBody::new( credential.clone(), proposal_id, - self.nymd_client.nymd.address().clone(), + self.nyxd_client.nyxd.address().clone(), ); - for client in self.api_clients.iter() { - self.nymd_client - .nymd + for client in api_clients { + self.nyxd_client + .nyxd .grant_allowance( &client.cosmos_address, vec![Coin::new(MAX_FEEGRANT_UNYM, self.mix_denom_base.clone())], @@ -106,8 +111,8 @@ impl CoconutVerifier { ) .await?; let ret = client.api_client.verify_bandwidth_credential(&req).await; - self.nymd_client - .nymd + self.nyxd_client + .nyxd .revoke_allowance( &client.cosmos_address, "Cleanup the previous allowance for releasing funds".to_string(), @@ -115,12 +120,12 @@ impl CoconutVerifier { ) .await?; if !ret?.verification_result { - debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.api_client.validator_api.current_url()); + debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.api_client.nym_api_client.current_url()); } } - self.nymd_client - .nymd + self.nyxd_client + .nyxd .execute_proposal(proposal_id, None) .await?; 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 e60159c564..eafc2992cf 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 @@ -2,14 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError; -use bandwidth_claim_contract::msg::ExecuteMsg; -use bandwidth_claim_contract::payment::LinkPaymentData; -use credentials::token::bandwidth::TokenCredential; -use crypto::asymmetric::identity::{PublicKey, Signature, SIGNATURE_LENGTH}; -use network_defaults::{ETH_EVENT_NAME, ETH_MIN_BLOCK_DEPTH}; +use nym_bandwidth_claim_contract::msg::ExecuteMsg; +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 validator_client::nymd::traits::MixnetQueryClient; -use validator_client::nymd::{AccountId, NymdClient, SigningNymdClient}; +use validator_client::nxmd::traits::MixnetQueryClient; +use validator_client::nyxd::{AccountId, NyxdClient, SigningNyxdClient}; 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 9e13fd2e69..ebd90206bc 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -2,14 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::client_handling::active_clients::ActiveClientsStore; -#[cfg(feature = "coconut")] use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; use crate::node::client_handling::websocket::connection_handler::{ AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream, }; use crate::node::storage::error::StorageError; use crate::node::storage::Storage; -use crypto::asymmetric::identity; use futures::{channel::mpsc, SinkExt, StreamExt}; use gateway_requests::authentication::encrypted_address::{ EncryptedAddressBytes, EncryptedAddressConversionError, @@ -21,7 +19,8 @@ use gateway_requests::types::{ClientControlRequest, ServerResponse}; use gateway_requests::{BinaryResponse, PROTOCOL_VERSION}; use log::*; use mixnet_client::forwarder::MixForwardingSender; -use nymsphinx::DestinationAddressBytes; +use nym_crypto::asymmetric::identity; +use nym_sphinx::DestinationAddressBytes; use rand::{CryptoRng, Rng}; use std::convert::TryFrom; use std::sync::Arc; @@ -30,7 +29,7 @@ use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError}; #[derive(Debug, Error)] -enum InitialAuthenticationError { +pub(crate) enum InitialAuthenticationError { #[error("Internal gateway storage error")] StorageError(#[from] StorageError), @@ -75,8 +74,6 @@ pub(crate) struct FreshHandler<R, S, St> { pub(crate) outbound_mix_sender: MixForwardingSender, pub(crate) socket_connection: SocketStream<S>, pub(crate) storage: St, - - #[cfg(feature = "coconut")] pub(crate) coconut_verifier: Arc<CoconutVerifier>, } @@ -98,7 +95,7 @@ where local_identity: Arc<identity::KeyPair>, storage: St, active_clients_store: ActiveClientsStore, - #[cfg(feature = "coconut")] coconut_verifier: Arc<CoconutVerifier>, + coconut_verifier: Arc<CoconutVerifier>, ) -> Self { FreshHandler { rng, @@ -108,20 +105,10 @@ where socket_connection: SocketStream::RawTcp(conn), local_identity, storage, - #[cfg(feature = "coconut")] coconut_verifier, } } - #[cfg(not(feature = "coconut"))] - /// Check that the local identity matches a given identity. - pub(crate) fn check_local_identity( - &self, - identity: &crypto::asymmetric::identity::PublicKey, - ) -> bool { - self.local_identity.public_key().eq(identity) - } - /// Attempts to perform websocket handshake with the remote and upgrades the raw TCP socket /// to the framed WebSocket. pub(crate) async fn perform_websocket_handshake(&mut self) -> Result<(), WsError> @@ -586,7 +573,7 @@ where let msg = match msg { Ok(msg) => msg, Err(err) => { - error!("failed to obtain message from websocket stream! stopping connection handler: {}", err); + error!("failed to obtain message from websocket stream! stopping connection handler: {err}"); break; } }; @@ -605,7 +592,7 @@ where if let Err(err) = self.send_websocket_message(err.into_error_message()).await { - debug!("Failed to send authentication error response - {}", err); + debug!("Failed to send authentication error response - {err}"); return None; } } @@ -614,7 +601,7 @@ where .send_websocket_message(auth_result.server_response.into()) .await { - debug!("Failed to send authentication response - {}", err); + debug!("Failed to send authentication response - {err}"); return None; } @@ -645,10 +632,7 @@ where ) .await { - debug!( - "Failed to send error response during authentication - {}", - err - ) + debug!("Failed to send error response during authentication: {err}",) } return None; } @@ -659,10 +643,10 @@ where None } - pub(crate) async fn start_handling(self) + pub(crate) async fn start_handling(self, shutdown: nym_task::TaskClient) where S: AsyncRead + AsyncWrite + Unpin + Send, { - super::handle_connection(self).await + super::handle_connection(self, shutdown).await } } 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 27b8366563..bdd3efa72d 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs @@ -1,21 +1,20 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 +use crate::node::storage::Storage; use gateway_requests::registration::handshake::SharedKeys; use gateway_requests::ServerResponse; use log::{trace, warn}; -use nymsphinx::DestinationAddressBytes; +use nym_sphinx::DestinationAddressBytes; +use nym_task::TaskClient; use rand::{CryptoRng, Rng}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::WebSocketStream; -use crate::node::storage::Storage; - pub(crate) use self::authenticated::AuthenticatedHandler; pub(crate) use self::fresh::FreshHandler; mod authenticated; -#[cfg(feature = "coconut")] pub(crate) mod coconut; mod fresh; @@ -66,26 +65,48 @@ impl InitialAuthResult { } } -pub(crate) async fn handle_connection<R, S, St>(mut handle: FreshHandler<R, S, St>) -where +pub(crate) async fn handle_connection<R, S, St>( + mut handle: FreshHandler<R, S, St>, + mut shutdown: TaskClient, +) where R: Rng + CryptoRng, S: AsyncRead + AsyncWrite + Unpin + Send, St: Storage, { - if let Err(err) = handle.perform_websocket_handshake().await { - warn!( - "Failed to complete WebSocket handshake - {}. Stopping the handler", - err - ); - return; + // If the connection handler abruptly stops, we shouldn't signal global shutdown + shutdown.mark_as_success(); + + match shutdown + .run_future(handle.perform_websocket_handshake()) + .await + { + None => { + trace!("received shutdown signal while performing websocket handshake"); + return; + } + Some(Err(err)) => { + warn!("Failed to complete WebSocket handshake: {err}. Stopping the handler"); + return; + } + _ => (), } trace!("Managed to perform websocket handshake!"); - if let Some(auth_handle) = handle.perform_initial_authentication().await { - auth_handle.listen_for_requests().await - } else { - warn!("Authentication has failed") + match shutdown + .run_future(handle.perform_initial_authentication()) + .await + { + None => { + trace!("received shutdown signal while performing initial authetnication"); + return; + } + Some(None) => { + warn!("authentication has failed"); + return; + } + Some(Some(auth_handle)) => auth_handle.listen_for_requests(shutdown).await, } + trace!("The handler is done!"); } diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index ee76f0213a..02882cebc0 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -2,26 +2,22 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::client_handling::active_clients::ActiveClientsStore; +use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; use crate::node::client_handling::websocket::connection_handler::FreshHandler; use crate::node::storage::Storage; -use crypto::asymmetric::identity; use log::*; use mixnet_client::forwarder::MixForwardingSender; +use nym_crypto::asymmetric::identity; use rand::rngs::OsRng; use std::net::SocketAddr; use std::process; use std::sync::Arc; use tokio::task::JoinHandle; -#[cfg(feature = "coconut")] -use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; - pub(crate) struct Listener { address: SocketAddr, local_identity: Arc<identity::KeyPair>, only_coconut_credentials: bool, - - #[cfg(feature = "coconut")] pub(crate) coconut_verifier: Arc<CoconutVerifier>, } @@ -30,13 +26,12 @@ impl Listener { address: SocketAddr, local_identity: Arc<identity::KeyPair>, only_coconut_credentials: bool, - #[cfg(feature = "coconut")] coconut_verifier: Arc<CoconutVerifier>, + coconut_verifier: Arc<CoconutVerifier>, ) -> Self { Listener { address, local_identity, only_coconut_credentials, - #[cfg(feature = "coconut")] coconut_verifier, } } @@ -48,6 +43,7 @@ impl Listener { outbound_mix_sender: MixForwardingSender, storage: St, active_clients_store: ActiveClientsStore, + mut shutdown: nym_task::TaskClient, ) where St: Storage + Clone + 'static, { @@ -55,31 +51,40 @@ impl Listener { let tcp_listener = match tokio::net::TcpListener::bind(self.address).await { Ok(listener) => listener, Err(err) => { - error!("Failed to bind the websocket to {} - {}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address, err); + error!("Failed to bind the websocket to {} - {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address); process::exit(1); } }; - loop { - match tcp_listener.accept().await { - Ok((socket, remote_addr)) => { - trace!("received a socket connection from {}", remote_addr); - // TODO: I think we *REALLY* need a mechanism for having a maximum number of connected - // clients or spawned tokio tasks -> perhaps a worker system? - let handle = FreshHandler::new( - OsRng, - socket, - self.only_coconut_credentials, - outbound_mix_sender.clone(), - Arc::clone(&self.local_identity), - storage.clone(), - active_clients_store.clone(), - #[cfg(feature = "coconut")] - Arc::clone(&self.coconut_verifier), - ); - tokio::spawn(async move { handle.start_handling().await }); + while !shutdown.is_shutdown() { + tokio::select! { + biased; + _ = shutdown.recv() => { + log::trace!("client_handling::Listener: received shutdown"); } - Err(e) => warn!("failed to get client: {:?}", e), + connection = tcp_listener.accept() => { + match connection { + Ok((socket, remote_addr)) => { + trace!("received a socket connection from {remote_addr}"); + // TODO: I think we *REALLY* need a mechanism for having a maximum number of connected + // clients or spawned tokio tasks -> perhaps a worker system? + let handle = FreshHandler::new( + OsRng, + socket, + self.only_coconut_credentials, + outbound_mix_sender.clone(), + Arc::clone(&self.local_identity), + storage.clone(), + active_clients_store.clone(), + Arc::clone(&self.coconut_verifier), + ); + let shutdown = shutdown.clone(); + tokio::spawn(async move { handle.start_handling(shutdown).await }); + } + Err(err) => warn!("failed to get client: {err}"), + } + } + } } } @@ -89,12 +94,13 @@ impl Listener { outbound_mix_sender: MixForwardingSender, storage: St, active_clients_store: ActiveClientsStore, + shutdown: nym_task::TaskClient, ) -> JoinHandle<()> where St: Storage + Clone + 'static, { tokio::spawn(async move { - self.run(outbound_mix_sender, storage, active_clients_store) + self.run(outbound_mix_sender, storage, active_clients_store, shutdown) .await }) } diff --git a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs index 8dd85d9ded..bd566f5c2f 100644 --- a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs +++ b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs @@ -10,10 +10,11 @@ use futures::StreamExt; use log::*; use mixnet_client::forwarder::MixForwardingSender; use mixnode_common::packet_processor::processor::ProcessedFinalHop; -use nymsphinx::forwarding::packet::MixPacket; -use nymsphinx::framing::codec::SphinxCodec; -use nymsphinx::framing::packet::FramedSphinxPacket; -use nymsphinx::DestinationAddressBytes; +use nym_sphinx::forwarding::packet::MixPacket; +use nym_sphinx::framing::codec::SphinxCodec; +use nym_sphinx::framing::packet::FramedSphinxPacket; +use nym_sphinx::DestinationAddressBytes; +use nym_task::TaskClient; use std::collections::HashMap; use std::net::SocketAddr; use tokio::net::TcpStream; @@ -142,7 +143,7 @@ impl<St: Storage> ConnectionHandler<St> { .store_processed_packet_payload(client_address, unsent_plaintext) .await { - Err(err) => error!("Failed to store client data - {}", err), + Err(err) => error!("Failed to store client data - {err}"), Ok(_) => trace!("Stored packet for {}", client_address), }, Ok(_) => trace!("Pushed received packet to {}", client_address), @@ -163,8 +164,8 @@ impl<St: Storage> ConnectionHandler<St> { let processed_final_hop = match self.packet_processor.process_received(framed_sphinx_packet) { - Err(e) => { - debug!("We failed to process received sphinx packet - {:?}", e); + Err(err) => { + debug!("We failed to process received sphinx packet - {err}"); return; } Ok(processed_final_hop) => processed_final_hop, @@ -173,26 +174,39 @@ impl<St: Storage> ConnectionHandler<St> { self.handle_processed_packet(processed_final_hop).await } - pub(crate) async fn handle_connection(mut self, conn: TcpStream, remote: SocketAddr) { + pub(crate) async fn handle_connection( + mut self, + conn: TcpStream, + remote: SocketAddr, + mut shutdown: TaskClient, + ) { debug!("Starting connection handler for {:?}", remote); + shutdown.mark_as_success(); let mut framed_conn = Framed::new(conn, SphinxCodec); - while let Some(framed_sphinx_packet) = framed_conn.next().await { - match framed_sphinx_packet { - Ok(framed_sphinx_packet) => { - // TODO: benchmark spawning tokio task with full processing vs just processing it - // synchronously under higher load in single and multi-threaded situation. - - // in theory we could process multiple sphinx packet from the same connection in parallel, - // but we already handle multiple concurrent connections so if anything, making - // that change would only slow things down - self.handle_received_packet(framed_sphinx_packet).await; + while !shutdown.is_shutdown() { + tokio::select! { + biased; + _ = shutdown.recv() => { + log::trace!("ConnectionHandler: received shutdown"); } - Err(err) => { - error!( - "The socket connection got corrupted with error: {:?}. Closing the socket", - err - ); - return; + Some(framed_sphinx_packet) = framed_conn.next() => { + match framed_sphinx_packet { + Ok(framed_sphinx_packet) => { + // TODO: benchmark spawning tokio task with full processing vs just processing it + // synchronously under higher load in single and multi-threaded situation. + + // in theory we could process multiple sphinx packet from the same connection in parallel, + // but we already handle multiple concurrent connections so if anything, making + // that change would only slow things down + self.handle_received_packet(framed_sphinx_packet).await; + } + Err(err) => { + error!( + "The socket connection got corrupted with error: {err}. Closing the socket", + ); + return; + } + } } } } diff --git a/gateway/src/node/mixnet_handling/receiver/listener.rs b/gateway/src/node/mixnet_handling/receiver/listener.rs index 1cb1f2850e..38fd8ef43b 100644 --- a/gateway/src/node/mixnet_handling/receiver/listener.rs +++ b/gateway/src/node/mixnet_handling/receiver/listener.rs @@ -4,18 +4,20 @@ use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; use crate::node::storage::Storage; use log::*; +use nym_task::TaskClient; use std::net::SocketAddr; use std::process; use tokio::task::JoinHandle; pub(crate) struct Listener { address: SocketAddr, + shutdown: TaskClient, } // TODO: this file is nearly identical to the one in mixnode impl Listener { - pub(crate) fn new(address: SocketAddr) -> Self { - Listener { address } + pub(crate) fn new(address: SocketAddr, shutdown: TaskClient) -> Self { + Listener { address, shutdown } } pub(crate) async fn run<St>(&mut self, connection_handler: ConnectionHandler<St>) @@ -26,18 +28,26 @@ impl Listener { let tcp_listener = match tokio::net::TcpListener::bind(self.address).await { Ok(listener) => listener, Err(err) => { - error!("Failed to bind to {} - {}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address, err); + error!("Failed to bind to {} - {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address); process::exit(1); } }; - loop { - match tcp_listener.accept().await { - Ok((socket, remote_addr)) => { - let handler = connection_handler.clone(); - tokio::spawn(handler.handle_connection(socket, remote_addr)); + while !self.shutdown.is_shutdown() { + tokio::select! { + biased; + _ = self.shutdown.recv() => { + log::trace!("mixnet_handling::Listener: Received shutdown"); + } + connection = tcp_listener.accept() => { + match connection { + Ok((socket, remote_addr)) => { + let handler = connection_handler.clone(); + tokio::spawn(handler.handle_connection(socket, remote_addr, self.shutdown.clone())); + } + Err(err) => warn!("failed to get client: {err}"), + } } - Err(e) => warn!("failed to get client: {:?}", e), } } } diff --git a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs index af9970cb47..e82014818f 100644 --- a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs +++ b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs @@ -1,26 +1,22 @@ // Copyright 2020 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crypto::asymmetric::encryption; use mixnode_common::packet_processor::error::MixProcessingError; pub use mixnode_common::packet_processor::processor::MixProcessingResult; use mixnode_common::packet_processor::processor::{ProcessedFinalHop, SphinxPacketProcessor}; -use nymsphinx::framing::packet::FramedSphinxPacket; +use nym_crypto::asymmetric::encryption; +use nym_sphinx::framing::packet::FramedSphinxPacket; +use thiserror::Error; -#[derive(Debug)] +#[derive(Error, Debug)] pub enum GatewayProcessingError { - PacketProcessingError(MixProcessingError), + #[error("failed to process received mix packet - {0}")] + PacketProcessingError(#[from] MixProcessingError), + + #[error("received a forward hop mix packet")] ForwardHopReceivedError, } -impl From<MixProcessingError> for GatewayProcessingError { - fn from(e: MixProcessingError) -> Self { - use GatewayProcessingError::*; - - PacketProcessingError(e) - } -} - // PacketProcessor contains all data required to correctly unwrap and store sphinx packets #[derive(Clone)] pub struct PacketProcessor { diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 1d23068db7..cf716d1e10 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -1,35 +1,32 @@ -// Copyright 2020 - Nym Technologies SA <contact@nymtech.net> +// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crate::commands::sign::load_identity_keys; -use crate::commands::validate_bech32_address_or_exit; +use self::storage::PersistentStorage; +use crate::commands::ensure_correct_bech32_prefix; +use crate::config::persistence::pathfinder::GatewayPathfinder; use crate::config::Config; +use crate::error::GatewayError; use crate::node::client_handling::active_clients::ActiveClientsStore; use crate::node::client_handling::websocket; +use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; use crate::node::statistics::collector::GatewayStatisticsCollector; use crate::node::storage::Storage; -use crypto::asymmetric::{encryption, identity}; +use crate::{commands::sign::load_identity_keys, OutputFormat}; +use colored::Colorize; use log::*; use mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; -#[cfg(feature = "coconut")] -use network_defaults::NymNetworkDetails; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_network_defaults::NymNetworkDetails; +use nym_statistics_common::collector::StatisticsSender; +use nym_task::{TaskClient, TaskManager}; use rand::seq::SliceRandom; use rand::thread_rng; -use statistics_common::collector::StatisticsSender; +use std::error::Error; use std::net::SocketAddr; use std::process; use std::sync::Arc; - -use crate::config::persistence::pathfinder::GatewayPathfinder; -#[cfg(feature = "coconut")] -use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; -#[cfg(feature = "coconut")] -use credentials::coconut::utils::obtain_aggregate_verification_key; -#[cfg(feature = "coconut")] -use validator_client::{Client, CoconutApiClient}; - -use self::storage::PersistentStorage; +use validator_client::Client; pub(crate) mod client_handling; pub(crate) mod mixnet_handling; @@ -46,7 +43,7 @@ async fn initialise_storage(config: &Config) -> PersistentStorage { let path = config.get_persistent_store_path(); let retrieval_limit = config.get_message_retrieval_limit(); match PersistentStorage::init(path, retrieval_limit).await { - Err(err) => panic!("failed to initialise gateway storage - {}", err), + Err(err) => panic!("failed to initialise gateway storage - {err}"), Ok(storage) => storage, } } @@ -94,7 +91,7 @@ where fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair { let identity_keypair: identity::KeyPair = - pemstore::load_keypair(&pemstore::KeyPairPath::new( + nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( pathfinder.private_identity_key().to_owned(), pathfinder.public_identity_key().to_owned(), )) @@ -104,7 +101,7 @@ where fn load_sphinx_keys(pathfinder: &GatewayPathfinder) -> encryption::KeyPair { let sphinx_keypair: encryption::KeyPair = - pemstore::load_keypair(&pemstore::KeyPairPath::new( + nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( pathfinder.private_encryption_key().to_owned(), pathfinder.public_encryption_key().to_owned(), )) @@ -114,47 +111,55 @@ where /// Signs the node config's bech32 address to produce a verification code for use in the wallet. /// Exits if the address isn't valid (which should protect against manual edits). - fn generate_owner_signature(&self) -> String { + fn generate_owner_signature(&self) -> Result<String, GatewayError> { let pathfinder = GatewayPathfinder::new_from_config(&self.config); let identity_keypair = load_identity_keys(&pathfinder); - let address = self.config.get_wallet_address(); - validate_bech32_address_or_exit(address); - let verification_code = identity_keypair.private_key().sign_text(address); - verification_code + let Some(address) = self.config.get_wallet_address() else { + let error_message = "Error: gateway hasn't set its wallet address".red(); + eprintln!("{error_message}"); + eprintln!("Exiting..."); + process::exit(1); + }; + // perform extra validation to ensure we have correct prefix + ensure_correct_bech32_prefix(&address)?; + let verification_code = identity_keypair.private_key().sign_text(address.as_ref()); + Ok(verification_code) } - pub(crate) fn print_node_details(&self) { - println!( - "Identity Key: {}", - self.identity_keypair.public_key().to_base58_string() - ); - println!( - "Sphinx Key: {}", - self.sphinx_keypair.public_key().to_base58_string() - ); - println!("Owner Signature: {}", self.generate_owner_signature()); - println!( - "Host: {} (bind address: {})", - self.config.get_announce_address(), - self.config.get_listening_address() - ); - println!("Version: {}", self.config.get_version()); - println!( - "Mix Port: {}, Clients port: {}", - self.config.get_mix_port(), - self.config.get_clients_port() - ); + pub(crate) fn print_node_details(&self, output: OutputFormat) -> Result<(), GatewayError> { + let node_details = nym_types::gateway::GatewayNodeDetailsResponse { + identity_key: self.identity_keypair.public_key().to_base58_string(), + sphinx_key: self.sphinx_keypair.public_key().to_base58_string(), + owner_signature: self.generate_owner_signature()?, + announce_address: self.config.get_announce_address(), + bind_address: self.config.get_listening_address().to_string(), + version: self.config.get_version().to_string(), + mix_port: self.config.get_mix_port(), + clients_port: self.config.get_clients_port(), + data_store: self + .config + .get_persistent_store_path() + .to_str() + .unwrap_or(".") + .to_string(), + }; - println!( - "Data store is at: {:?}", - self.config.get_persistent_store_path() - ); + match output { + OutputFormat::Json => println!( + "{}", + serde_json::to_string(&node_details) + .unwrap_or_else(|_| "Could not serialize node details".to_string()) + ), + OutputFormat::Text => println!("{}", node_details), + } + Ok(()) } fn start_mix_socket_listener( &self, ack_sender: MixForwardingSender, active_clients_store: ActiveClientsStore, + shutdown: TaskClient, ) { info!("Starting mix socket listener..."); @@ -173,14 +178,15 @@ where self.config.get_mix_port(), ); - mixnet_handling::Listener::new(listening_address).start(connection_handler); + mixnet_handling::Listener::new(listening_address, shutdown).start(connection_handler); } fn start_client_websocket_listener( &self, forwarding_channel: MixForwardingSender, active_clients_store: ActiveClientsStore, - #[cfg(feature = "coconut")] coconut_verifier: Arc<CoconutVerifier>, + shutdown: TaskClient, + coconut_verifier: Arc<CoconutVerifier>, ) { info!("Starting client [web]socket listener..."); @@ -193,17 +199,17 @@ where listening_address, Arc::clone(&self.identity_keypair), self.config.get_only_coconut_credentials(), - #[cfg(feature = "coconut")] coconut_verifier, ) .start( forwarding_channel, self.storage.clone(), active_clients_store, + shutdown, ); } - fn start_packet_forwarder(&self) -> MixForwardingSender { + fn start_packet_forwarder(&self, shutdown: TaskClient) -> MixForwardingSender { info!("Starting mix packet forwarder..."); let (mut packet_forwarder, packet_sender) = PacketForwarder::new( @@ -212,39 +218,36 @@ where self.config.get_initial_connection_timeout(), self.config.get_maximum_connection_buffer_size(), self.config.get_use_legacy_sphinx_framing(), + shutdown, ); tokio::spawn(async move { packet_forwarder.run().await }); packet_sender } - async fn wait_for_interrupt(&self) { - if let Err(e) = tokio::signal::ctrl_c().await { - error!( - "There was an error while capturing SIGINT - {:?}. We will terminate regardless", - e - ); - } - println!( - "Received SIGINT - the gateway will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)." - ); + async fn wait_for_interrupt( + &self, + shutdown: TaskManager, + ) -> Result<(), Box<dyn Error + Send + Sync>> { + let res = shutdown.catch_interrupt().await; + log::info!("Stopping nym gateway"); + res } - fn random_api_client(&self) -> validator_client::ApiClient { - let endpoints = self.config.get_validator_api_endpoints(); - let validator_api = endpoints + fn random_api_client(&self) -> validator_client::NymApiClient { + let endpoints = self.config.get_nym_api_endpoints(); + let nym_api = endpoints .choose(&mut thread_rng()) .expect("The list of validator apis is empty"); - validator_client::ApiClient::new(validator_api.clone()) + validator_client::NymApiClient::new(nym_api.clone()) } - #[cfg(feature = "coconut")] - fn random_nymd_client( + fn random_nyxd_client( &self, - ) -> validator_client::Client<validator_client::nymd::SigningNymdClient> { - let endpoints = self.config.get_validator_nymd_endpoints(); - let validator_nymd = endpoints + ) -> validator_client::Client<validator_client::nyxd::SigningNyxdClient> { + let endpoints = self.config.get_nyxd_urls(); + let validator_nyxd = endpoints .choose(&mut thread_rng()) .expect("The list of validators is empty"); @@ -257,71 +260,68 @@ where let mut client = Client::new_signing(client_config, self.config.get_cosmos_mnemonic()) .expect("Could not connect with mnemonic"); client - .change_nymd(validator_nymd.clone()) - .expect("Could not use the random nymd URL"); + .change_nyxd(validator_nyxd.clone()) + .expect("Could not use the random nyxd URL"); client } - // TODO: ask DH whether this function still makes sense in ^0.10 - async fn check_if_same_ip_gateway_exists(&self) -> Option<String> { + async fn check_if_same_ip_gateway_exists(&self) -> Result<Option<String>, GatewayError> { let validator_client = self.random_api_client(); let existing_gateways = match validator_client.get_cached_gateways().await { Ok(gateways) => gateways, Err(err) => { - error!("failed to grab initial network gateways - {}\n Please try to startup again in few minutes", err); - process::exit(1); + error!("failed to grab initial network gateways - {err}\n Please try to startup again in few minutes"); + return Err(GatewayError::NetworkGatewaysQueryFailure { source: err }); } }; let our_host = self.config.get_announce_address(); - existing_gateways + Ok(existing_gateways .iter() .find(|node| node.gateway.host == our_host) - .map(|node| node.gateway().identity_key.clone()) + .map(|node| node.gateway().identity_key.clone())) } - pub async fn run(&mut self) { - info!("Starting nym gateway!"); - - if let Some(duplicate_node_key) = self.check_if_same_ip_gateway_exists().await { - if duplicate_node_key == self.identity_keypair.public_key().to_base58_string() { + async fn ensure_no_duplicate_host_exists(&self) -> Result<(), GatewayError> { + let local_identity = self.identity_keypair.public_key().to_base58_string(); + if let Some(remote_identity) = self.check_if_same_ip_gateway_exists().await? { + if remote_identity == local_identity { warn!("We seem to have not unregistered after going offline - there's a node with identical identity and announce-host as us registered.") } else { error!( - "Our announce-host is identical to an existing node's announce-host! (its key is {:?})", - duplicate_node_key + "Our announce-host is identical to an existing node's announce-host! (its key is {remote_identity})", ); - return; + return Err(GatewayError::DuplicateNodeHost { + host: self.config.get_announce_address(), + local_identity, + remote_identity, + }); } } + Ok(()) + } + + pub async fn run(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> { + info!("Starting nym gateway!"); + + self.ensure_no_duplicate_host_exists().await?; + + let shutdown = TaskManager::new(10); - #[cfg(feature = "coconut")] let coconut_verifier = { - let nymd_client = self.random_nymd_client(); - let api_clients = CoconutApiClient::all_coconut_api_clients(&nymd_client) - .await - .expect("Could not query all api clients"); - let validators_verification_key = obtain_aggregate_verification_key(&api_clients) - .await - .expect("failed to contact validators to obtain their verification keys"); - CoconutVerifier::new( - api_clients, - nymd_client, - std::env::var(network_defaults::var_names::MIX_DENOM) - .expect("mix denom base not set"), - validators_verification_key, - ) - .expect("Could not create coconut verifier") + let nyxd_client = self.random_nyxd_client(); + CoconutVerifier::new(nyxd_client) }; - let mix_forwarding_channel = self.start_packet_forwarder(); + let mix_forwarding_channel = self.start_packet_forwarder(shutdown.subscribe()); let active_clients_store = ActiveClientsStore::new(); self.start_mix_socket_listener( mix_forwarding_channel.clone(), active_clients_store.clone(), + shutdown.subscribe(), ); if self.config.get_enabled_statistics() { @@ -340,12 +340,12 @@ where self.start_client_websocket_listener( mix_forwarding_channel, active_clients_store, - #[cfg(feature = "coconut")] + shutdown.subscribe(), Arc::new(coconut_verifier), ); info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!"); - self.wait_for_interrupt().await + self.wait_for_interrupt(shutdown).await } } diff --git a/gateway/src/node/statistics/collector.rs b/gateway/src/node/statistics/collector.rs index 62014d0d16..4f1035d979 100644 --- a/gateway/src/node/statistics/collector.rs +++ b/gateway/src/node/statistics/collector.rs @@ -6,7 +6,7 @@ use sqlx::types::chrono::{DateTime, Utc}; use std::time::Duration; use url::Url; -use statistics_common::{ +use nym_statistics_common::{ api::build_and_send_statistics_request, collector::StatisticsCollector, error::StatsError, StatsData, StatsGatewayData, StatsMessage, }; diff --git a/gateway/src/node/storage/mod.rs b/gateway/src/node/storage/mod.rs index aeed709455..94e5b0c5dc 100644 --- a/gateway/src/node/storage/mod.rs +++ b/gateway/src/node/storage/mod.rs @@ -9,7 +9,7 @@ use crate::node::storage::shared_keys::SharedKeysManager; use async_trait::async_trait; use gateway_requests::registration::handshake::SharedKeys; use log::{debug, error}; -use nymsphinx::DestinationAddressBytes; +use nym_sphinx::DestinationAddressBytes; use sqlx::ConnectOptions; use std::path::Path; @@ -173,13 +173,13 @@ impl PersistentStorage { let connection_pool = match sqlx::SqlitePool::connect_with(opts).await { Ok(db) => db, Err(err) => { - error!("Failed to connect to SQLx database: {}", err); + error!("Failed to connect to SQLx database: {err}"); return Err(err.into()); } }; if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await { - error!("Failed to perform migration on the SQLx database: {}", err); + error!("Failed to perform migration on the SQLx database: {err}"); return Err(err.into()); } diff --git a/gateway/src/support/config.rs b/gateway/src/support/config.rs new file mode 100644 index 0000000000..4a6bda91a4 --- /dev/null +++ b/gateway/src/support/config.rs @@ -0,0 +1,29 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::commands::{override_config, OverrideConfig}; +use crate::config::Config; +use crate::error::GatewayError; +use log::error; +use nym_config::NymConfig; + +pub(crate) fn build_config<O: Into<OverrideConfig>>( + id: String, + override_args: O, +) -> Result<Config, GatewayError> { + let config = match Config::load_from_file(&id) { + Ok(cfg) => cfg, + Err(err) => { + error!( + "Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})", + ); + return Err(GatewayError::ConfigLoadFailure { + path: Config::default_config_file_path(&id), + id, + source: err, + }); + } + }; + + override_config(config, override_args.into()) +} diff --git a/gateway/src/support/mod.rs b/gateway/src/support/mod.rs new file mode 100644 index 0000000000..6c7248da0b --- /dev/null +++ b/gateway/src/support/mod.rs @@ -0,0 +1,4 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod config; diff --git a/integrations/bity/Cargo.toml b/integrations/bity/Cargo.toml index feae848a70..327d3aeeaa 100644 --- a/integrations/bity/Cargo.toml +++ b/integrations/bity/Cargo.toml @@ -15,7 +15,7 @@ cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegr nym-cli-commands = { path = "../../common/commands" } validator-client = { path = "../../common/client-libs/validator-client", features = [ - "nymd-client", + "nyxd-client", ] } [dev-dependencies] diff --git a/integrations/bity/src/lib.rs b/integrations/bity/src/lib.rs index 9af11eecfb..0523b9e6ec 100644 --- a/integrations/bity/src/lib.rs +++ b/integrations/bity/src/lib.rs @@ -8,13 +8,13 @@ mod tests { use crate::{sign, verify}; use cosmrs::AccountId; use std::str::FromStr; - use validator_client::nymd::wallet::DirectSecp256k1HdWallet; + use validator_client::nyxd::wallet::DirectSecp256k1HdWallet; fn get_order(prefix: &str) -> anyhow::Result<(OrderSignature, String)> { let mnemonic = "crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove"; - let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.parse().unwrap())?; + let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.parse()?); - let accounts = wallet.try_derive_accounts().unwrap(); + let accounts = wallet.try_derive_accounts()?; let message = "This is the order message from Bity"; let signature = sign::sign_order(&wallet, &accounts[0], message.to_string())?; diff --git a/integrations/bity/src/sign/mod.rs b/integrations/bity/src/sign/mod.rs index feed824868..65de7536c6 100644 --- a/integrations/bity/src/sign/mod.rs +++ b/integrations/bity/src/sign/mod.rs @@ -1,13 +1,13 @@ use crate::order::OrderSignature; -use validator_client::nymd::error::NymdError; -use validator_client::nymd::wallet::{AccountData, DirectSecp256k1HdWallet}; +use validator_client::nyxd::error::NyxdError; +use validator_client::nyxd::wallet::{AccountData, DirectSecp256k1HdWallet}; /// Signs an order message to purchase Nym with Bity pub fn sign_order( wallet: &DirectSecp256k1HdWallet, signer: &AccountData, message: String, -) -> Result<OrderSignature, NymdError> { +) -> Result<OrderSignature, NyxdError> { Ok(OrderSignature { account_id: signer.address().clone(), public_key: signer.public_key(), diff --git a/lerna.json b/lerna.json index cc6884af07..5d17094fd5 100644 --- a/lerna.json +++ b/lerna.json @@ -1,7 +1,10 @@ { "packages": [ "ts-packages/*", - "nym-wallet" + "nym-wallet", + "nym-connect/**", + "sdk/typescript/examples/docs", + "sdk/typescript/packages/**" ], "version": "0.0.0" } diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 08714e3a04..30dafa2b3e 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-mixnode" -version = "1.1.4" +version = "1.1.14" authors = [ "Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>", @@ -18,20 +18,20 @@ rust-version = "1.58.1" [dependencies] anyhow = "1.0.40" bs58 = "0.4.0" -clap = { version = "3.2", features = ["cargo", "derive"] } +clap = { version = "4.0", features = ["cargo", "derive"] } colored = "2.0" cupid = "0.6.1" dirs = "4.0" -dotenv = "0.15.0" futures = "0.3.0" humantime-serde = "1.0" lazy_static = "1.4.0" -log = "0.4.0" +log = { workspace = true } pretty_env_logger = "0.4.0" rand = "0.7.3" rocket = { version = "0.5.0-rc.2", features = ["json"] } serde = { version="1.0", features = ["derive"] } -sysinfo = "0.24.1" +serde_json = "1.0" +sysinfo = "0.27.7" tokio = { version="1.21.2", features = ["rt-multi-thread", "net", "signal"] } tokio-util = { version="0.7.3", features = ["codec"] } toml = "0.5.8" @@ -42,27 +42,24 @@ tracing-opentelemetry = "0.18.0" opentelemetry-jaeger = {version = "0.17.0", features = ["collector_client","rt-tokio","isahc_collector_client"]} opentelemetry = {version = "0.18.0", features = ["rt-tokio"]} tracing-flame = "0.2.0" +atty = "0.2" ## internal -config = { path="../common/config" } -crypto = { path="../common/crypto" } -completions = { path="../common/completions" } -logging = { path="../common/logging" } +nym-config = { path="../common/config" } +nym-crypto = { path="../common/crypto" } mixnet-client = { path="../common/client-libs/mixnet-client" } mixnode-common = { path="../common/mixnode-common" } -nonexhaustive-delayqueue = { path="../common/nonexhaustive-delayqueue" } -nymsphinx = { path="../common/nymsphinx" } -pemstore = { path="../common/pemstore" } -task = { path = "../common/task" } -topology = { path="../common/topology" } +nym-nonexhaustive-delayqueue = { path="../common/nonexhaustive-delayqueue" } +nym-sphinx = { path="../common/nymsphinx" } +nym-pemstore = { path = "../common/pemstore", version = "0.2.0" } +nym-task = { path = "../common/task" } +nym-types = { path = "../common/types" } +nym-topology = { path="../common/topology" } validator-client = { path="../common/client-libs/validator-client" } -version-checker = { path="../common/version-checker" } +nym-bin-common = { path="../common/bin-common" } [dev-dependencies] tokio = { version="1.21.2", features = ["rt-multi-thread", "net", "signal", "test-util"] } -nymsphinx-types = { path = "../common/nymsphinx/types" } -nymsphinx-params = { path = "../common/nymsphinx/params" } - -[build-dependencies] -vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] } +nym-sphinx-types = { path = "../common/nymsphinx/types" } +nym-sphinx-params = { path = "../common/nymsphinx/params" } diff --git a/mixnode/build.rs b/mixnode/build.rs deleted file mode 100644 index 01b3a20dc6..0000000000 --- a/mixnode/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> -// SPDX-License-Identifier: Apache-2.0 - -use vergen::{vergen, Config}; - -fn main() { - vergen(Config::default()).expect("failed to extract build metadata") -} diff --git a/mixnode/src/commands/describe.rs b/mixnode/src/commands/describe.rs index 2bab09b8c9..160e5de633 100644 --- a/mixnode/src/commands/describe.rs +++ b/mixnode/src/commands/describe.rs @@ -1,8 +1,11 @@ +// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + use crate::config::Config; use crate::node::node_description::NodeDescription; use clap::Args; use colored::Colorize; -use config::NymConfig; +use nym_config::NymConfig; use std::io; use std::io::Write; @@ -11,45 +14,64 @@ pub(crate) struct Describe { /// The id of the mixnode you want to describe #[clap(long)] id: String, + + /// Human readable name of this node + #[clap(long)] + name: Option<String>, + + /// Description of this node + #[clap(long)] + description: Option<String>, + + /// Link associated with this node, for example `https://mixnode.yourdomain.com` + #[clap(long)] + link: Option<String>, + + /// Physical location of this node, for example `City: London, Country: UK` + #[clap(long)] + location: Option<String>, } -pub(crate) fn execute(args: &Describe) { +fn read_user_input() -> String { + io::stdout().flush().unwrap(); + let mut buf = String::new(); + io::stdin().read_line(&mut buf).unwrap(); + buf.trim().to_string() +} + +pub(crate) fn execute(args: Describe) { // ensure that the mixnode has in fact been initialized - match Config::load_from_file(Some(&args.id)) { + match Config::load_from_file(&args.id) { Ok(cfg) => cfg, Err(err) => { - error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", &args.id, err); + error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", &args.id); return; } }; - // get input from the user - print!("name: "); - io::stdout().flush().unwrap(); - let mut name_buf = String::new(); - io::stdin().read_line(&mut name_buf).unwrap(); - let name = name_buf.trim().to_string(); - - print!("description: "); - io::stdout().flush().unwrap(); - let mut desc_buf = String::new(); - io::stdin().read_line(&mut desc_buf).unwrap(); - let description = desc_buf.trim().to_string(); - let example_url = "https://mixnode.yourdomain.com".bright_cyan(); let example_location = "City: London, Country: UK"; - print!("link, e.g. {}: ", example_url); - io::stdout().flush().unwrap(); - let mut link_buf = String::new(); - io::stdin().read_line(&mut link_buf).unwrap(); - let link = link_buf.trim().to_string(); + // get input from the user if not provided via the arguments + let name = args.name.unwrap_or_else(|| { + print!("name: "); + read_user_input() + }); - print!("location, e.g. {}: ", example_location); - io::stdout().flush().unwrap(); - let mut location_buf = String::new(); - io::stdin().read_line(&mut location_buf).unwrap(); - let location = location_buf.trim().to_string(); + let description = args.description.unwrap_or_else(|| { + print!("description: "); + read_user_input() + }); + + let link = args.link.unwrap_or_else(|| { + print!("link, e.g. {example_url}: "); + read_user_input() + }); + + let location = args.location.unwrap_or_else(|| { + print!("location, e.g. {example_location}: "); + read_user_input() + }); let node_description = NodeDescription { name, @@ -61,7 +83,7 @@ pub(crate) fn execute(args: &Describe) { // save the struct NodeDescription::save_to_file( &node_description, - Config::default_config_directory(Some(&args.id)), + Config::default_config_directory(&args.id), ) .unwrap() } diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index 7d848c7849..9dc60f2369 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -1,14 +1,16 @@ -// Copyright 2020 - Nym Technologies SA <contact@nymtech.net> +// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 +use super::OverrideConfig; use crate::config::Config; use crate::node::MixNode; +use crate::OutputFormat; use crate::{commands::override_config, config::persistence::pathfinder::MixNodePathfinder}; use clap::Args; -use config::NymConfig; -use crypto::asymmetric::{encryption, identity}; - -use super::OverrideConfig; +use nym_config::NymConfig; +use nym_crypto::asymmetric::{encryption, identity}; +use std::net::IpAddr; +use validator_client::nyxd; #[derive(Args, Clone)] pub(crate) struct Init { @@ -18,11 +20,11 @@ pub(crate) struct Init { /// The host on which the mixnode will be running #[clap(long)] - host: String, + host: IpAddr, /// The wallet address you will use to bond this mixnode, e.g. nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9 #[clap(long)] - wallet_address: String, + wallet_address: nyxd::AccountId, /// The port on which the mixnode will be listening for mix packets #[clap(long)] @@ -40,9 +42,10 @@ pub(crate) struct Init { #[clap(long)] announce_host: Option<String>, - /// Comma separated list of rest endpoints of the validators - #[clap(long)] - validators: Option<String>, + /// Comma separated list of nym-api endpoints of the validators + // the alias here is included for backwards compatibility (1.1.4 and before) + #[clap(long, alias = "validators", value_delimiter = ',')] + nym_apis: Option<Vec<url::Url>>, } impl From<Init> for OverrideConfig { @@ -55,18 +58,18 @@ impl From<Init> for OverrideConfig { verloc_port: init_config.verloc_port, http_api_port: init_config.http_api_port, announce_host: init_config.announce_host, - validators: init_config.validators, + nym_apis: init_config.nym_apis, } } } -pub(crate) fn execute(args: &Init) { +pub(crate) fn execute(args: &Init, output: OutputFormat) { let override_config_fields = OverrideConfig::from(args.clone()); let id = &override_config_fields.id; - println!("Initialising mixnode {}...", id); + eprintln!("Initialising mixnode {id}..."); - let already_init = if Config::default_config_file_path(Some(id)).exists() { - println!("Mixnode \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id); + let already_init = if Config::default_config_file_path(id).exists() { + eprintln!("Mixnode \"{id}\" was already initialised before! Config information will be overwritten (but keys will be kept)!"); true } else { false @@ -82,33 +85,32 @@ pub(crate) fn execute(args: &Init) { let identity_keys = identity::KeyPair::new(&mut rng); let sphinx_keys = encryption::KeyPair::new(&mut rng); let pathfinder = MixNodePathfinder::new_from_config(&config); - pemstore::store_keypair( + nym_pemstore::store_keypair( &identity_keys, - &pemstore::KeyPairPath::new( + &nym_pemstore::KeyPairPath::new( pathfinder.private_identity_key().to_owned(), pathfinder.public_identity_key().to_owned(), ), ) .expect("Failed to save identity keys"); - pemstore::store_keypair( + nym_pemstore::store_keypair( &sphinx_keys, - &pemstore::KeyPairPath::new( + &nym_pemstore::KeyPairPath::new( pathfinder.private_encryption_key().to_owned(), pathfinder.public_encryption_key().to_owned(), ), ) .expect("Failed to save sphinx keys"); - - println!("Saved mixnet identity and sphinx keypairs"); + eprintln!("Saved mixnet identity and sphinx keypairs"); } let config_save_location = config.get_config_file_save_location(); config .save_to_file(None) .expect("Failed to save the config file"); - println!("Saved configuration file to {:?}", config_save_location); - println!("Mixnode configuration completed.\n\n\n"); + eprintln!("Saved configuration file to {config_save_location:?}"); + eprintln!("Mixnode configuration completed.\n\n\n"); - MixNode::new(config).print_node_details() + MixNode::new(config).print_node_details(output) } diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index f716953c11..6854595a46 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -1,19 +1,18 @@ // Copyright 2020 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use std::process; - use crate::{config::Config, Cli}; use clap::CommandFactory; use clap::Subcommand; use colored::Colorize; -use completions::{fig_generate, ArgShell}; -use config::defaults::mainnet::read_var_if_not_default; -use config::{ - defaults::var_names::{API_VALIDATOR, BECH32_PREFIX, CONFIGURED}, - parse_validators, -}; -use crypto::bech32_address_validation; +use nym_bin_common::completions::{fig_generate, ArgShell}; +use nym_bin_common::version_checker; +use nym_config::defaults::var_names::{BECH32_PREFIX, NYM_API}; +use nym_config::OptionalSet; +use nym_crypto::bech32_address_validation; +use std::net::IpAddr; +use std::process; +use validator_client::nyxd; mod describe; mod init; @@ -52,71 +51,64 @@ pub(crate) enum Commands { // Configuration that can be overridden. struct OverrideConfig { id: String, - host: Option<String>, - wallet_address: Option<String>, + host: Option<IpAddr>, + wallet_address: Option<nyxd::AccountId>, mix_port: Option<u16>, verloc_port: Option<u16>, http_api_port: Option<u16>, announce_host: Option<String>, - validators: Option<String>, + nym_apis: Option<Vec<url::Url>>, } pub(crate) async fn execute(args: Cli) { let bin_name = "nym-mixnode"; - match &args.command { + let output = args.output(); + + match args.command { Commands::Describe(m) => describe::execute(m), - Commands::Init(m) => init::execute(m), - Commands::Run(m) => run::execute(m).await, - Commands::Sign(m) => sign::execute(m), - Commands::Upgrade(m) => upgrade::execute(m), - Commands::NodeDetails(m) => node_details::execute(m), - Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name), - Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name), + Commands::Init(m) => init::execute(&m, output), + Commands::Run(m) => run::execute(&m, output).await, + Commands::Sign(m) => sign::execute(&m), + Commands::Upgrade(m) => upgrade::execute(&m), + Commands::NodeDetails(m) => node_details::execute(&m, output), + Commands::Completions(s) => s.generate(&mut crate::Cli::command(), bin_name), + Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::command(), bin_name), } } fn override_config(mut config: Config, args: OverrideConfig) -> Config { + // special case that I'm not sure could be easily handled with the trait let mut was_host_overridden = false; if let Some(host) = args.host { config = config.with_listening_address(host); was_host_overridden = true; } - if let Some(port) = args.mix_port { - config = config.with_mix_port(port); - } - - if let Some(port) = args.verloc_port { - config = config.with_verloc_port(port); - } - - if let Some(port) = args.http_api_port { - config = config.with_http_api_port(port); - } - - if let Some(ref raw_validators) = args.validators { - config = config.with_custom_validator_apis(parse_validators(raw_validators)); - } else if std::env::var(CONFIGURED).is_ok() { - if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) { - config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators)) - } - } - - if let Some(ref announce_host) = args.announce_host { + if let Some(announce_host) = args.announce_host { config = config.with_announce_address(announce_host); } else if was_host_overridden { // make sure our 'announce-host' always defaults to 'host' config = config.announce_address_from_listening_address() } - if let Some(ref wallet_address) = args.wallet_address { - let trimmed = wallet_address.trim(); - validate_bech32_address_or_exit(trimmed); - config = config.with_wallet_address(trimmed); - } - config + .with_optional(Config::with_mix_port, args.mix_port) + .with_optional(Config::with_verloc_port, args.verloc_port) + .with_optional(Config::with_http_api_port, args.http_api_port) + .with_optional_custom_env( + Config::with_custom_nym_apis, + args.nym_apis, + NYM_API, + nym_config::parse_urls, + ) + .with_optional( + |cfg, wallet_address| { + validate_bech32_address_or_exit(wallet_address.as_ref()); + cfg.with_wallet_address(wallet_address) + }, + args.wallet_address, + ) } /// Ensures that a given bech32 address is valid, or exits @@ -125,18 +117,18 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) { if let Err(bech32_address_validation::Bech32Error::DecodeFailed(err)) = bech32_address_validation::try_bech32_decode(address) { - let error_message = format!("Error: wallet address decoding failed: {}", err).red(); - println!("{}", error_message); - println!("Exiting..."); + let error_message = format!("Error: wallet address decoding failed: {err}").red(); + error!("{}", error_message); + error!("Exiting..."); process::exit(1); } if let Err(bech32_address_validation::Bech32Error::WrongPrefix(err)) = bech32_address_validation::validate_bech32_prefix(&prefix, address) { - let error_message = format!("Error: wallet address type is wrong, {}", err).red(); - println!("{}", error_message); - println!("Exiting..."); + let error_message = format!("Error: wallet address type is wrong, {err}").red(); + error!("{}", error_message); + error!("Exiting..."); process::exit(1); } } diff --git a/mixnode/src/commands/node_details.rs b/mixnode/src/commands/node_details.rs index 471b3bde94..9f1d0745a7 100644 --- a/mixnode/src/commands/node_details.rs +++ b/mixnode/src/commands/node_details.rs @@ -3,8 +3,9 @@ use crate::config::Config; use crate::node::MixNode; +use crate::OutputFormat; use clap::Args; -use config::NymConfig; +use nym_config::NymConfig; #[derive(Args)] pub(crate) struct NodeDetails { @@ -13,8 +14,8 @@ pub(crate) struct NodeDetails { id: String, } -pub(crate) fn execute(args: &NodeDetails) { - let config = match Config::load_from_file(Some(&args.id)) { +pub(crate) fn execute(args: &NodeDetails, output: OutputFormat) { + let config = match Config::load_from_file(&args.id) { Ok(cfg) => cfg, Err(err) => { error!( @@ -26,5 +27,5 @@ pub(crate) fn execute(args: &NodeDetails) { } }; - MixNode::new(config).print_node_details() + MixNode::new(config).print_node_details(output) } diff --git a/mixnode/src/commands/run.rs b/mixnode/src/commands/run.rs index c0106034b9..6c2509d6a7 100644 --- a/mixnode/src/commands/run.rs +++ b/mixnode/src/commands/run.rs @@ -1,13 +1,15 @@ // Copyright 2020 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 +use super::OverrideConfig; use crate::commands::{override_config, version_check}; use crate::config::Config; use crate::node::MixNode; +use crate::OutputFormat; use clap::Args; -use config::NymConfig; - -use super::OverrideConfig; +use nym_config::NymConfig; +use std::net::IpAddr; +use validator_client::nyxd; #[derive(Args, Clone)] pub(crate) struct Run { @@ -17,11 +19,11 @@ pub(crate) struct Run { /// The custom host on which the mixnode will be running #[clap(long)] - host: Option<String>, + host: Option<IpAddr>, /// The wallet address you will use to bond this mixnode, e.g. nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9 #[clap(long)] - wallet_address: Option<String>, + wallet_address: Option<nyxd::AccountId>, /// The port on which the mixnode will be listening for mix packets #[clap(long)] @@ -39,9 +41,10 @@ pub(crate) struct Run { #[clap(long)] announce_host: Option<String>, - /// Comma separated list of rest endpoints of the validators - #[clap(long)] - validators: Option<String>, + /// Comma separated list of nym-api endpoints of the validators + // the alias here is included for backwards compatibility (1.1.4 and before) + #[clap(long, alias = "validators", value_delimiter = ',')] + nym_apis: Option<Vec<url::Url>>, } impl From<Run> for OverrideConfig { @@ -54,7 +57,7 @@ impl From<Run> for OverrideConfig { verloc_port: run_config.verloc_port, http_api_port: run_config.http_api_port, announce_host: run_config.announce_host, - validators: run_config.validators, + nym_apis: run_config.nym_apis, } } } @@ -62,10 +65,9 @@ impl From<Run> for OverrideConfig { fn show_binding_warning(address: &str) { println!("\n##### NOTE #####"); println!( - "\nYou are trying to bind to {} - you might not be accessible to other nodes\n\ + "\nYou are trying to bind to {address} - you might not be accessible to other nodes\n\ You can ignore this note if you're running setup on a local network \n\ - or have set a custom 'announce-host'", - address + or have set a custom 'announce-host'" ); println!("\n\n"); } @@ -74,10 +76,10 @@ fn special_addresses() -> Vec<&'static str> { vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"] } -pub(crate) async fn execute(args: &Run) { - println!("Starting mixnode {}...", args.id); +pub(crate) async fn execute(args: &Run, output: OutputFormat) { + eprintln!("Starting mixnode {}...", args.id); - let mut config = match Config::load_from_file(Some(&args.id)) { + let mut config = match Config::load_from_file(&args.id) { Ok(cfg) => cfg, Err(err) => { error!( @@ -102,10 +104,10 @@ pub(crate) async fn execute(args: &Run) { let mut mixnode = MixNode::new(config); - println!( + eprintln!( "\nTo bond your mixnode you will need to install the Nym wallet, go to https://nymtech.net/get-involved and select the Download button.\n\ Select the correct version and install it to your machine. You will need to provide the following: \n "); - mixnode.print_node_details(); + mixnode.print_node_details(output); mixnode.run().await } diff --git a/mixnode/src/commands/sign.rs b/mixnode/src/commands/sign.rs index 46158a9b42..6c5aa74e3b 100644 --- a/mixnode/src/commands/sign.rs +++ b/mixnode/src/commands/sign.rs @@ -1,4 +1,4 @@ -// Copyright 2020 - Nym Technologies SA <contact@nymtech.net> +// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 use std::convert::TryFrom; @@ -8,22 +8,24 @@ use crate::config::{persistence::pathfinder::MixNodePathfinder, Config}; use crate::node::MixNode; use anyhow::{anyhow, Result}; use clap::{ArgGroup, Args}; -use config::NymConfig; -use crypto::asymmetric::identity; use log::error; +use nym_config::NymConfig; +use nym_crypto::asymmetric::identity; +use validator_client::nyxd; use super::version_check; #[derive(Args, Clone)] -#[clap(group(ArgGroup::new("sign").required(true).args(&["address", "text"])))] +#[clap(group(ArgGroup::new("sign").required(true).args(&["wallet_address", "text"])))] pub(crate) struct Sign { /// The id of the mixnode you want to sign with #[clap(long)] id: String, /// Signs your blockchain address with your identity key - #[clap(long)] - address: Option<String>, + // the alias here is included for backwards compatibility (1.1.4 and before) + #[clap(long, alias = "address")] + wallet_address: Option<nyxd::AccountId>, /// Signs an arbitrary piece of text with your identity key #[clap(long)] @@ -32,7 +34,7 @@ pub(crate) struct Sign { enum SignedTarget { Text(String), - Address(String), + Address(nyxd::AccountId), } impl TryFrom<Sign> for SignedTarget { @@ -41,7 +43,7 @@ impl TryFrom<Sign> for SignedTarget { fn try_from(args: Sign) -> Result<Self, Self::Error> { if let Some(text) = args.text { Ok(SignedTarget::Text(text)) - } else if let Some(address) = args.address { + } else if let Some(address) = args.wallet_address { Ok(SignedTarget::Address(address)) } else { // This is unreachable, and hopefully clap will support it explicitly by outputting an @@ -52,33 +54,24 @@ impl TryFrom<Sign> for SignedTarget { } } -fn print_signed_address(private_key: &identity::PrivateKey, raw_address: &str) { - let trimmed = raw_address.trim(); - validate_bech32_address_or_exit(trimmed); - let signature = private_key.sign_text(trimmed); +fn print_signed_address(private_key: &identity::PrivateKey, wallet_address: nyxd::AccountId) { + // perform extra validation to ensure we have correct prefix + validate_bech32_address_or_exit(wallet_address.as_ref()); - println!( - "The base58-encoded signature on '{}' is: {}", - trimmed, signature - ); + let signature = private_key.sign_text(wallet_address.as_ref()); + println!("The base58-encoded signature on '{wallet_address}' is: {signature}",); } fn print_signed_text(private_key: &identity::PrivateKey, text: &str) { - println!( - "Signing the text {:?} using your mixnode's Ed25519 identity key...", - text - ); + println!("Signing the text {text:?} using your mixnode's Ed25519 identity key..."); let signature = private_key.sign_text(text); - println!( - "The base58-encoded signature on '{}' is: {}", - text, signature - ); + println!("The base58-encoded signature on '{text}' is: {signature}"); } pub(crate) fn execute(args: &Sign) { - let config = match Config::load_from_file(Some(&args.id)) { + let config = match Config::load_from_file(&args.id) { Ok(cfg) => cfg, Err(err) => { error!( @@ -107,6 +100,6 @@ pub(crate) fn execute(args: &Sign) { match signed_target { SignedTarget::Text(text) => print_signed_text(identity_keypair.private_key(), &text), - SignedTarget::Address(addr) => print_signed_address(identity_keypair.private_key(), &addr), + SignedTarget::Address(addr) => print_signed_address(identity_keypair.private_key(), addr), } } diff --git a/mixnode/src/commands/upgrade.rs b/mixnode/src/commands/upgrade.rs index d104d47988..ea636cdf14 100644 --- a/mixnode/src/commands/upgrade.rs +++ b/mixnode/src/commands/upgrade.rs @@ -3,10 +3,10 @@ use crate::config::{missing_string_value, Config}; use clap::Args; -use config::NymConfig; +use nym_bin_common::version_checker::Version; +use nym_config::NymConfig; use std::fmt::Display; use std::process; -use version_checker::Version; #[derive(Args)] pub(crate) struct Upgrade { @@ -22,49 +22,38 @@ fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! } fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) { - println!( - "\n==================\nTrying to upgrade mixnode from {} to {} ...", - from, to - ); + println!("\n==================\nTrying to upgrade mixnode from {from} to {to} ..."); } fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) { - eprintln!( - "Upgrade from {} to {} failed!\n==================\n", - from, to - ); + eprintln!("Upgrade from {from} to {to} failed!\n==================\n"); } fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) { - println!( - "Upgrade from {} to {} was successful!\n==================\n", - from, to - ); + println!("Upgrade from {from} to {to} was successful!\n==================\n"); } fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! { eprintln!( - "Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!", - config_version, package_version + "Cannot perform upgrade from {config_version} to {package_version}. Your version is too old to perform the upgrade.!" ); process::exit(1) } fn unsupported_upgrade(config_version: &Version, package_version: &Version) -> ! { - eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, package_version); + eprintln!("Cannot perform upgrade from {config_version} to {package_version}. Please let the developers know about this issue if you expected it to work!"); process::exit(1) } fn parse_config_version(config: &Config) -> Version { let version = Version::parse(config.get_version()).unwrap_or_else(|err| { - eprintln!("failed to parse client version! - {:?}", err); + eprintln!("failed to parse client version! - {err}"); process::exit(1) }); if version.is_prerelease() || !version.build.is_empty() { eprintln!( - "Trying to upgrade from a non-released version {}. This is not supported!", - version + "Trying to upgrade from a non-released version {version}. This is not supported!" ); process::exit(1) } @@ -79,10 +68,7 @@ fn parse_package_version() -> Version { // however, we are not using them ourselves at the moment and hence it should be fine. // if we change our mind, we could easily tweak this code if version.is_prerelease() || !version.build.is_empty() { - eprintln!( - "Trying to upgrade to a non-released version {}. This is not supported!", - version - ); + eprintln!("Trying to upgrade to a non-released version {version}. This is not supported!"); process::exit(1) } @@ -106,7 +92,7 @@ fn minor_0_12_upgrade( let upgraded_config = config.with_custom_version(to_version.to_string().as_ref()); upgraded_config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); + eprintln!("failed to overwrite config file! - {err}"); print_failed_upgrade(config_version, &to_version); process::exit(1); }); @@ -139,8 +125,8 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) { pub(crate) fn execute(args: &Upgrade) { let package_version = parse_package_version(); - let existing_config = Config::load_from_file(Some(&args.id)).unwrap_or_else(|err| { - eprintln!("failed to load existing config file! - {:?}", err); + let existing_config = Config::load_from_file(&args.id).unwrap_or_else(|err| { + eprintln!("failed to load existing config file! - {err}"); process::exit(1) }); diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 3f5208cee8..85e928c902 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -2,17 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::template::config_template; -use config::defaults::mainnet::API_VALIDATOR; -use config::defaults::{ +use nym_config::defaults::mainnet::NYM_API; +use nym_config::defaults::{ DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, }; -use config::NymConfig; +use nym_config::NymConfig; use serde::{Deserialize, Deserializer, Serialize}; use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; use url::Url; +use validator_client::nyxd; pub mod persistence; mod template; @@ -155,21 +156,13 @@ impl Config { self } - pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec<Url>) -> Self { - self.mixnode.validator_api_urls = validator_api_urls; + pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec<Url>) -> Self { + self.mixnode.nym_api_urls = nym_api_urls; self } - pub fn with_listening_address<S: Into<String>>(mut self, listening_address: S) -> Self { - let listening_address_string = listening_address.into(); - if let Ok(ip_addr) = listening_address_string.parse() { - self.mixnode.listening_address = ip_addr - } else { - error!( - "failed to change listening address. the provided value ({}) was invalid", - listening_address_string - ) - } + pub fn with_listening_address(mut self, listening_address: IpAddr) -> Self { + self.mixnode.listening_address = listening_address; self } @@ -203,8 +196,8 @@ impl Config { self } - pub fn with_wallet_address(mut self, wallet_address: &str) -> Self { - self.mixnode.wallet_address = wallet_address.to_string(); + pub fn with_wallet_address(mut self, wallet_address: nyxd::AccountId) -> Self { + self.mixnode.wallet_address = Some(wallet_address); self } @@ -233,8 +226,8 @@ impl Config { self.mixnode.public_sphinx_key_file.clone() } - pub fn get_validator_api_endpoints(&self) -> Vec<Url> { - self.mixnode.validator_api_urls.clone() + pub fn get_nym_api_endpoints(&self) -> Vec<Url> { + self.mixnode.nym_api_urls.clone() } pub fn get_node_stats_logging_delay(&self) -> Duration { @@ -317,8 +310,8 @@ impl Config { self.verloc.retry_timeout } - pub fn get_wallet_address(&self) -> &str { - &self.mixnode.wallet_address + pub fn get_wallet_address(&self) -> Option<nyxd::AccountId> { + self.mixnode.wallet_address.clone() } } @@ -369,32 +362,33 @@ struct MixNode { /// Path to file containing public sphinx key. public_sphinx_key_file: PathBuf, - /// Addresses to APIs running on validator from which the node gets the view of the network. - validator_api_urls: Vec<Url>, + /// Addresses to nym APIs from which the node gets the view of the network. + nym_api_urls: Vec<Url>, /// nym_home_directory specifies absolute path to the home nym MixNodes directory. /// It is expected to use default value and hence .toml file should not redefine this field. nym_root_directory: PathBuf, /// The Cosmos wallet address that will control this mixnode - wallet_address: String, + // the only reason this is an Option is because of the lack of existence of a sane default value + wallet_address: Option<nyxd::AccountId>, } impl MixNode { fn default_private_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_identity.pem") + Config::default_data_directory(id).join("private_identity.pem") } fn default_public_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_identity.pem") + Config::default_data_directory(id).join("public_identity.pem") } fn default_private_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_sphinx.pem") + Config::default_data_directory(id).join("private_sphinx.pem") } fn default_public_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_sphinx.pem") + Config::default_data_directory(id).join("public_sphinx.pem") } } @@ -412,9 +406,9 @@ impl Default for MixNode { public_identity_key_file: Default::default(), private_sphinx_key_file: Default::default(), public_sphinx_key_file: Default::default(), - validator_api_urls: vec![Url::from_str(API_VALIDATOR).expect("Invalid default API URL")], + nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")], nym_root_directory: Config::default_root_directory(), - wallet_address: "nymXXXXXXXX".to_string(), + wallet_address: None, } } } diff --git a/mixnode/src/config/template.rs b/mixnode/src/config/template.rs index 2af12a195b..f222275083 100644 --- a/mixnode/src/config/template.rs +++ b/mixnode/src/config/template.rs @@ -54,8 +54,8 @@ verloc_port = {{ mixnode.verloc_port }} http_api_port = {{ mixnode.http_api_port }} # Addresses to APIs running on validator from which the node gets the view of the network. -validator_api_urls = [ - {{#each mixnode.validator_api_urls }} +nym_api_urls = [ + {{#each mixnode.nym_api_urls }} '{{this}}', {{/each}} ] diff --git a/mixnode/src/main.rs b/mixnode/src/main.rs index f332b678bd..0a1d3f6430 100644 --- a/mixnode/src/main.rs +++ b/mixnode/src/main.rs @@ -1,41 +1,68 @@ +// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + #[macro_use] extern crate rocket; -// Copyright 2020 - Nym Technologies SA <contact@nymtech.net> -// SPDX-License-Identifier: Apache-2.0 - -use ::config::defaults::setup_env; -use clap::{crate_version, Parser}; +use ::nym_config::defaults::setup_env; +use clap::{crate_name, crate_version, Parser, ValueEnum}; use lazy_static::lazy_static; -use logging::setup_logging; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::{EnvFilter, Registry, filter}; use tracing_flame::FlameLayer; +use nym_bin_common::logging::setup_logging; +use nym_bin_common::{build_information::BinaryBuildInformation, logging::banner}; mod commands; mod config; mod node; lazy_static! { - pub static ref LONG_VERSION: String = long_version(); + pub static ref PRETTY_BUILD_INFORMATION: String = + BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print(); } // Helper for passing LONG_VERSION to clap -fn long_version_static() -> &'static str { - &LONG_VERSION +fn pretty_build_info_static() -> &'static str { + &PRETTY_BUILD_INFORMATION +} + +#[derive(Clone, ValueEnum)] +enum OutputFormat { + Json, + Text, +} + +impl Default for OutputFormat { + fn default() -> Self { + OutputFormat::Text + } } #[derive(Parser)] -#[clap(author = "Nymtech", version, about, long_version = long_version_static())] +#[clap(author = "Nymtech", version, about, long_version = pretty_build_info_static())] struct Cli { /// Path pointing to an env file that configures the mixnode. #[clap(short, long)] pub(crate) config_env_file: Option<std::path::PathBuf>, + #[clap(short, long)] + pub(crate) output: Option<OutputFormat>, + #[clap(subcommand)] command: commands::Commands, } +impl Cli { + fn output(&self) -> OutputFormat { + if let Some(ref output) = self.output { + output.clone() + } else { + OutputFormat::default() + } + } +} + #[tokio::main] async fn main() { //setup_logging(); @@ -64,62 +91,16 @@ async fn main() { tracing::subscriber::set_global_default(subscriber) .expect("Failed to set global subscriber"); - println!("{}", banner()); + if atty::is(atty::Stream::Stdout) { + println!("{}", banner(crate_name!(), crate_version!())); + } let args = Cli::parse(); - setup_env(args.config_env_file.clone()); + setup_env(args.config_env_file.as_ref()); commands::execute(args).await; opentelemetry::global::shutdown_tracer_provider(); } -fn banner() -> String { - format!( - r#" - - _ __ _ _ _ __ ___ - | '_ \| | | | '_ \ _ \ - | | | | |_| | | | | | | - |_| |_|\__, |_| |_| |_| - |___/ - - (mixnode - version {:}) - - "#, - crate_version!() - ) -} - -fn long_version() -> String { - format!( - r#" -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -{:<20}{} -"#, - "Build Timestamp:", - env!("VERGEN_BUILD_TIMESTAMP"), - "Build Version:", - env!("VERGEN_BUILD_SEMVER"), - "Commit SHA:", - env!("VERGEN_GIT_SHA"), - "Commit Date:", - env!("VERGEN_GIT_COMMIT_TIMESTAMP"), - "Commit Branch:", - env!("VERGEN_GIT_BRANCH"), - "rustc Version:", - env!("VERGEN_RUSTC_SEMVER"), - "rustc Channel:", - env!("VERGEN_RUSTC_CHANNEL"), - "cargo Profile:", - env!("VERGEN_CARGO_PROFILE") - ) -} - #[cfg(test)] mod tests { use super::*; diff --git a/mixnode/src/node/listener/connection_handler/mod.rs b/mixnode/src/node/listener/connection_handler/mod.rs index d8ab263279..fa32becb61 100644 --- a/mixnode/src/node/listener/connection_handler/mod.rs +++ b/mixnode/src/node/listener/connection_handler/mod.rs @@ -5,15 +5,15 @@ use crate::node::listener::connection_handler::packet_processing::{ MixProcessingResult, PacketProcessor, }; use crate::node::packet_delayforwarder::PacketDelayForwardSender; -use crate::node::ShutdownListener; +use crate::node::TaskClient; use futures::StreamExt; use tracing::{error, info, trace, debug, warn}; use tracing::*; -use nymsphinx::forwarding::packet::MixPacket; -use nymsphinx::params::PacketSize; -use nymsphinx::framing::codec::SphinxCodec; -use nymsphinx::framing::packet::FramedSphinxPacket; -use nymsphinx::Delay as SphinxDelay; +use nym_sphinx::forwarding::packet::MixPacket; +use nym_sphinx::params::PacketSize; +use nym_sphinx::framing::codec::SphinxCodec; +use nym_sphinx::framing::packet::FramedSphinxPacket; +use nym_sphinx::Delay as SphinxDelay; use std::net::SocketAddr; use tokio::net::TcpStream; use tokio::time::Instant; @@ -63,7 +63,7 @@ impl ConnectionHandler { // all processing such, key caching, etc. was done. // however, if it was a forward hop, we still need to delay it match self.packet_processor.process_received(framed_sphinx_packet) { - Err(e) => debug!("We failed to process received sphinx packet - {:?}", e), + Err(err) => debug!("We failed to process received sphinx packet - {err}"), Ok(res) => match res { MixProcessingResult::ForwardHop(forward_packet, delay) => { self.delay_and_forward_packet(forward_packet, delay) @@ -79,12 +79,17 @@ impl ConnectionHandler { self, conn: TcpStream, remote: SocketAddr, - mut shutdown: ShutdownListener, + mut shutdown: TaskClient, ) { debug!("Starting connection handler for {:?}", remote); + shutdown.mark_as_success(); let mut framed_conn = Framed::new(conn, SphinxCodec); while !shutdown.is_shutdown() { tokio::select! { + biased; + _ = shutdown.recv() => { + trace!("ConnectionHandler: received shutdown"); + } Some(framed_sphinx_packet) = framed_conn.next() => { match framed_sphinx_packet { Ok(framed_sphinx_packet) => { @@ -102,16 +107,12 @@ impl ConnectionHandler { } Err(err) => { error!( - "The socket connection got corrupted with error: {:?}. Closing the socket", - err + "The socket connection got corrupted with error: {err}. Closing the socket", ); return; } } }, - _ = shutdown.recv() => { - trace!("ConnectionHandler: received shutdown"); - } } } diff --git a/mixnode/src/node/listener/connection_handler/packet_processing.rs b/mixnode/src/node/listener/connection_handler/packet_processing.rs index ac080a249a..06580031a9 100644 --- a/mixnode/src/node/listener/connection_handler/packet_processing.rs +++ b/mixnode/src/node/listener/connection_handler/packet_processing.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::node_statistics; -use crypto::asymmetric::encryption; use mixnode_common::packet_processor::error::MixProcessingError; pub use mixnode_common::packet_processor::processor::MixProcessingResult; use mixnode_common::packet_processor::processor::SphinxPacketProcessor; -use nymsphinx::framing::packet::FramedSphinxPacket; +use nym_crypto::asymmetric::encryption; +use nym_sphinx::framing::packet::FramedSphinxPacket; // PacketProcessor contains all data required to correctly unwrap and forward sphinx packets #[derive(Clone)] diff --git a/mixnode/src/node/listener/mod.rs b/mixnode/src/node/listener/mod.rs index cb991bcf2a..6721d2618b 100644 --- a/mixnode/src/node/listener/mod.rs +++ b/mixnode/src/node/listener/mod.rs @@ -9,17 +9,17 @@ use std::process; use tokio::net::TcpListener; use tokio::task::JoinHandle; -use super::ShutdownListener; +use super::TaskClient; pub(crate) mod connection_handler; pub(crate) struct Listener { address: SocketAddr, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl Listener { - pub(crate) fn new(address: SocketAddr, shutdown: ShutdownListener) -> Self { + pub(crate) fn new(address: SocketAddr, shutdown: TaskClient) -> Self { Listener { address, shutdown } } #[instrument(level="info", skip_all, "Mixnet Listener")] @@ -28,25 +28,26 @@ impl Listener { let listener = match TcpListener::bind(self.address).await { Ok(listener) => listener, Err(err) => { - error!("Failed to bind to {} - {}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address, err); + error!("Failed to bind to {} - {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address); process::exit(1); } }; while !self.shutdown.is_shutdown() { tokio::select! { + biased; + _ = self.shutdown.recv() => { + trace!("Listener: Received shutdown"); + } connection = listener.accept() => { match connection { Ok((socket, remote_addr)) => { let handler = connection_handler.clone(); tokio::spawn(handler.handle_connection(socket, remote_addr, self.shutdown.clone()).instrument(info_span!("Connection handling", address = %remote_addr))); } - Err(err) => warn!("Failed to accept incoming connection - {:?}", err), + Err(err) => warn!("Failed to accept incoming connection - {err}"), } }, - _ = self.shutdown.recv() => { - trace!("Listener: Received shutdown"); - } }; } trace!("Listener: Exiting"); diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 6dbdf83407..312e5a4bb7 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -17,18 +17,20 @@ use crate::node::listener::Listener; use crate::node::node_description::NodeDescription; use crate::node::node_statistics::SharedNodeStats; use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender}; -use ::crypto::asymmetric::{encryption, identity}; -use config::NymConfig; use tracing::*; use tracing::{info, error, warn}; +use crate::OutputFormat; +use colored::Colorize; use mixnode_common::verloc::{self, AtomicVerlocResult, VerlocMeasurer}; +use nym_bin_common::version_checker::parse_version; +use nym_config::NymConfig; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_task::{TaskClient, TaskManager}; use rand::seq::SliceRandom; use rand::thread_rng; use std::net::SocketAddr; use std::process; use std::sync::Arc; -use task::{wait_for_signal, ShutdownListener, ShutdownNotifier}; -use version_checker::parse_version; mod http; mod listener; @@ -57,14 +59,14 @@ impl MixNode { } fn load_node_description(config: &Config) -> NodeDescription { - NodeDescription::load_from_file(Config::default_config_directory(Some(&config.get_id()))) + NodeDescription::load_from_file(Config::default_config_directory(&config.get_id())) .unwrap_or_default() } /// Loads identity keys stored on disk pub(crate) fn load_identity_keys(pathfinder: &MixNodePathfinder) -> identity::KeyPair { let identity_keypair: identity::KeyPair = - pemstore::load_keypair(&pemstore::KeyPairPath::new( + nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( pathfinder.private_identity_key().to_owned(), pathfinder.public_identity_key().to_owned(), )) @@ -75,7 +77,7 @@ impl MixNode { /// Loads Sphinx keys stored on disk fn load_sphinx_keys(pathfinder: &MixNodePathfinder) -> encryption::KeyPair { let sphinx_keypair: encryption::KeyPair = - pemstore::load_keypair(&pemstore::KeyPairPath::new( + nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( pathfinder.private_encryption_key().to_owned(), pathfinder.public_encryption_key().to_owned(), )) @@ -88,39 +90,41 @@ impl MixNode { fn generate_owner_signature(&self) -> String { let pathfinder = MixNodePathfinder::new_from_config(&self.config); let identity_keypair = Self::load_identity_keys(&pathfinder); - let address = self.config.get_wallet_address(); - validate_bech32_address_or_exit(address); - let verification_code = identity_keypair.private_key().sign_text(address); + let Some(address) = self.config.get_wallet_address() else { + let error_message = "Error: mixnode hasn't set its wallet address".red(); + println!("{error_message}"); + println!("Exiting..."); + process::exit(1); + }; + // perform extra validation to ensure we have correct prefix + validate_bech32_address_or_exit(address.as_ref()); + let verification_code = identity_keypair.private_key().sign_text(address.as_ref()); verification_code } /// Prints relevant node details to the console - pub(crate) fn print_node_details(&self) { - println!( - "Identity Key: {}", - self.identity_keypair.public_key().to_base58_string() - ); - println!( - "Sphinx Key: {}", - self.sphinx_keypair.public_key().to_base58_string() - ); - println!("Owner Signature: {}", self.generate_owner_signature()); - println!( - "Host: {} (bind address: {})", - self.config.get_announce_address(), - self.config.get_listening_address() - ); - println!("Version: {}", self.config.get_version()); - println!( - "Mix Port: {}, Verloc port: {}, Http Port: {}\n", - self.config.get_mix_port(), - self.config.get_verloc_port(), - self.config.get_http_api_port() - ); - println!( - "You are bonding to wallet address: {}\n\n", - self.config.get_wallet_address() - ); + pub(crate) fn print_node_details(&self, output: OutputFormat) { + let node_details = nym_types::mixnode::MixnodeNodeDetailsResponse { + identity_key: self.identity_keypair.public_key().to_base58_string(), + sphinx_key: self.sphinx_keypair.public_key().to_base58_string(), + owner_signature: self.generate_owner_signature(), + announce_address: self.config.get_announce_address(), + bind_address: self.config.get_listening_address().to_string(), + version: self.config.get_version().to_string(), + mix_port: self.config.get_mix_port(), + http_api_port: self.config.get_http_api_port(), + verloc_port: self.config.get_verloc_port(), + wallet_address: self.config.get_wallet_address().map(|x| x.to_string()), + }; + + match output { + OutputFormat::Json => println!( + "{}", + serde_json::to_string(&node_details) + .unwrap_or_else(|_| "Could not serialize node details".to_string()) + ), + OutputFormat::Text => println!("{node_details}"), + } } fn start_http_api( @@ -154,7 +158,7 @@ impl MixNode { fn start_node_stats_controller( &self, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> (SharedNodeStats, node_statistics::UpdateSender) { info!("Starting node stats controller..."); let controller = node_statistics::Controller::new( @@ -172,7 +176,7 @@ impl MixNode { &self, node_stats_update_sender: node_statistics::UpdateSender, delay_forwarding_channel: PacketDelayForwardSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { info!("Starting socket listener..."); @@ -192,7 +196,7 @@ impl MixNode { fn start_packet_delay_forwarder( &mut self, node_stats_update_sender: node_statistics::UpdateSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> PacketDelayForwardSender { info!("Starting packet delay-forwarder..."); @@ -216,7 +220,7 @@ impl MixNode { packet_sender } - fn start_verloc_measurements(&self, shutdown: ShutdownListener) -> AtomicVerlocResult { + fn start_verloc_measurements(&self, shutdown: TaskClient) -> AtomicVerlocResult { info!("Starting the round-trip-time measurer..."); // this is a sanity check to make sure we didn't mess up with the minimum version at some point @@ -246,7 +250,7 @@ impl MixNode { .tested_nodes_batch_size(self.config.get_measurement_tested_nodes_batch_size()) .testing_interval(self.config.get_measurement_testing_interval()) .retry_timeout(self.config.get_measurement_retry_timeout()) - .validator_api_urls(self.config.get_validator_api_endpoints()) + .nym_api_urls(self.config.get_nym_api_endpoints()) .build(); let mut verloc_measurer = @@ -256,13 +260,13 @@ impl MixNode { atomic_verloc_results } - fn random_api_client(&self) -> validator_client::client::ApiClient { - let endpoints = self.config.get_validator_api_endpoints(); - let validator_api = endpoints + fn random_api_client(&self) -> validator_client::NymApiClient { + let endpoints = self.config.get_nym_api_endpoints(); + let nym_api = endpoints .choose(&mut thread_rng()) .expect("The list of validator apis is empty"); - validator_client::client::ApiClient::new(validator_api.clone()) + validator_client::NymApiClient::new(nym_api.clone()) } // TODO: ask DH whether this function still makes sense in ^0.10 @@ -289,15 +293,8 @@ impl MixNode { .map(|node| node.bond_information.mix_node.identity_key.clone()) } - async fn wait_for_interrupt(&self, mut shutdown: ShutdownNotifier) { - wait_for_signal().await; - - info!("Sending shutdown"); - shutdown.signal_shutdown().ok(); - - info!("Waiting for tasks to finish... (Press ctrl-c to force)"); - shutdown.wait_for_shutdown().await; - + async fn wait_for_interrupt(&self, shutdown: TaskManager) { + let _res = shutdown.catch_interrupt().await; info!("Stopping nym mixnode"); } @@ -316,7 +313,7 @@ impl MixNode { } } - let shutdown = ShutdownNotifier::default(); + let shutdown = TaskManager::default(); let (node_stats_pointer, node_stats_update_sender) = self.start_node_stats_controller(shutdown.subscribe()); diff --git a/mixnode/src/node/node_statistics.rs b/mixnode/src/node/node_statistics.rs index 86f98401ab..2187d77a91 100644 --- a/mixnode/src/node/node_statistics.rs +++ b/mixnode/src/node/node_statistics.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use std::time::{Duration, SystemTime}; use tokio::sync::{RwLock, RwLockReadGuard}; -use super::ShutdownListener; +use super::TaskClient; // convenience aliases type PacketsMap = HashMap<String, u64>; @@ -211,14 +211,14 @@ impl CurrentPacketData { struct UpdateHandler { current_data: CurrentPacketData, update_receiver: PacketDataReceiver, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl UpdateHandler { fn new( current_data: CurrentPacketData, update_receiver: PacketDataReceiver, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { UpdateHandler { current_data, @@ -293,7 +293,7 @@ struct StatsUpdater { updating_delay: Duration, current_packet_data: CurrentPacketData, current_stats: SharedNodeStats, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl StatsUpdater { @@ -301,7 +301,7 @@ impl StatsUpdater { updating_delay: Duration, current_packet_data: CurrentPacketData, current_stats: SharedNodeStats, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { StatsUpdater { updating_delay, @@ -335,11 +335,11 @@ impl StatsUpdater { struct PacketStatsConsoleLogger { logging_delay: Duration, stats: SharedNodeStats, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl PacketStatsConsoleLogger { - fn new(logging_delay: Duration, stats: SharedNodeStats, shutdown: ShutdownListener) -> Self { + fn new(logging_delay: Duration, stats: SharedNodeStats, shutdown: TaskClient) -> Self { PacketStatsConsoleLogger { logging_delay, stats, @@ -451,7 +451,7 @@ impl Controller { pub(crate) fn new( logging_delay: Duration, stats_updating_delay: Duration, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { let (sender, receiver) = mpsc::unbounded(); let shared_packet_data = CurrentPacketData::new(); @@ -503,13 +503,13 @@ impl Controller { #[cfg(test)] mod tests { use super::*; - use task::ShutdownNotifier; + use nym_task::TaskManager; #[tokio::test] async fn node_stats_reported_are_received() { let logging_delay = Duration::from_millis(20); let stats_updating_delay = Duration::from_millis(10); - let shutdown = ShutdownNotifier::default(); + let shutdown = TaskManager::default(); let node_stats_controller = Controller::new(logging_delay, stats_updating_delay, shutdown.subscribe()); diff --git a/mixnode/src/node/packet_delayforwarder.rs b/mixnode/src/node/packet_delayforwarder.rs index 74a1ebfb95..e8ed08c20e 100644 --- a/mixnode/src/node/packet_delayforwarder.rs +++ b/mixnode/src/node/packet_delayforwarder.rs @@ -4,15 +4,15 @@ use crate::node::node_statistics::UpdateSender; use futures::channel::mpsc; use futures::StreamExt; -use nymsphinx::params::packet_sizes::PacketSize; -use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue}; -use nymsphinx::forwarding::packet::MixPacket; +use nym_sphinx::params::packet_sizes::PacketSize; +use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue}; +use nym_sphinx::forwarding::packet::MixPacket; use std::io; use tokio::time::Instant; use tracing::*; use tracing::{trace}; -use super::ShutdownListener; +use super::TaskClient; // Delay + MixPacket vs Instant + MixPacket @@ -31,7 +31,7 @@ where packet_sender: PacketDelayForwardSender, packet_receiver: PacketDelayForwardReceiver, node_stats_update_sender: UpdateSender, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl<C> DelayForwarder<C> @@ -41,7 +41,7 @@ where pub(crate) fn new( client: C, node_stats_update_sender: UpdateSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> DelayForwarder<C> { let (packet_sender, packet_receiver) = mpsc::unbounded(); @@ -139,13 +139,13 @@ mod tests { use std::sync::{Arc, Mutex}; use std::time::Duration; - use task::ShutdownNotifier; + use nym_task::TaskManager; - use nymsphinx::addressing::nodes::NymNodeRoutingAddress; - use nymsphinx_params::packet_sizes::PacketSize; - use nymsphinx_params::PacketMode; - use nymsphinx_types::builder::SphinxPacketBuilder; - use nymsphinx_types::{ + use nym_sphinx::addressing::nodes::NymNodeRoutingAddress; + use nym_sphinx_params::packet_sizes::PacketSize; + use nym_sphinx_params::PacketMode; + use nym_sphinx_types::builder::SphinxPacketBuilder; + use nym_sphinx_types::{ crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes, SphinxPacket, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH, }; @@ -210,7 +210,7 @@ mod tests { let node_stats_update_sender = UpdateSender::new(stats_sender); let client = TestClient::default(); let client_packets_sent = client.packets_sent.clone(); - let shutdown = ShutdownNotifier::default(); + let shutdown = TaskManager::default(); let mut delay_forwarder = DelayForwarder::new(client, node_stats_update_sender, shutdown.subscribe()); let packet_sender = delay_forwarder.sender(); diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml new file mode 100644 index 0000000000..b1f76ffae6 --- /dev/null +++ b/nym-api/Cargo.toml @@ -0,0 +1,110 @@ +# Copyright 2020 - Nym Technologies SA <contact@nymtech.net> +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "nym-api" +version = "1.1.14" +authors = [ + "Dave Hrycyszyn <futurechimp@users.noreply.github.com>", + "Jędrzej Stuczyński <andrew@nymtech.net>", + "Drazen Urch <durch@users.noreply.github.com>", +] +edition = "2021" +rust-version = "1.56" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = { workspace = true } +bs58 = {version = "0.4.0" } +bip39 = "1" +cfg-if = "1.0" +clap = { version = "4.0", 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 = "0.3.24" +humantime-serde = "1.0" +lazy_static = "1.4.0" +log = { workspace = true } +pin-project = "1.0" +pretty_env_logger = "0.4.0" +rand = "0.8.5" +rand-07 = { package = "rand", version = "0.7.3" } # required for compatibility +reqwest = { version = "0.11.11", features = ["json"] } +rocket = { version = "0.5.0-rc.2", features = ["json"] } +rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "dfd3662c49e2f6fc37df35091cb94d82f7fb5915" } +serde = "1.0" +serde_json = "1.0" +tap = "1.0" +thiserror = "1.0" +time = { version = "0.3.14", features = ["serde-human-readable", "parsing"] } +tokio = { version = "1.24.1", features = [ + "rt-multi-thread", + "macros", + "signal", + "time", +] } +tokio-stream = "0.1.11" +url = "2.2" + +ts-rs = {version = "6.1", optional = true} + +anyhow = "1.0" +getset = "0.1.1" + +sqlx = { version = "0.6.2", features = [ + "runtime-tokio-rustls", + "sqlite", + "macros", + "migrate", +] } + +okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] } +rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] } +schemars = { version = "0.8", features = ["preserve_order"] } + +## internal +nym-coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } +nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" } +nym-coconut-interface = { path = "../common/coconut-interface" } +nym-config = { path = "../common/config" } +cosmwasm-std = { workspace = true } +nym-credential-storage = { path = "../common/credential-storage" } +nym-credentials = { path = "../common/credentials" } +nym-crypto = { path = "../common/crypto" } +cw3 = { workspace = true } +cw4 = { workspace = true } +nym-dkg = { path = "../common/dkg", features = ["cw-types"] } +gateway-client = { path = "../common/client-libs/gateway-client" } +nym-inclusion-probability = { path = "../common/inclusion-probability" } +nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-vesting-contract-common = { path = "../common/cosmwasm-smart-contracts/vesting-contract" } +nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } +nym-multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } +nymcoconut = { path = "../common/nymcoconut" } +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" } +validator-client = { path = "../common/client-libs/validator-client", features = [ + "nyxd-client", +] } +nym-bin-common = { path = "../common/bin-common" } + +[features] +no-reward = [] +generate-ts = ["ts-rs"] + +[build-dependencies] +tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } +sqlx = { version = "0.6.2", features = [ + "runtime-tokio-rustls", + "sqlite", + "macros", + "migrate", +] } + +[dev-dependencies] +cw3 = { workspace = true } +cw-utils = { workspace = true } diff --git a/validator-api/README.md b/nym-api/README.md similarity index 81% rename from validator-api/README.md rename to nym-api/README.md index 9ed9b223cd..7d3d6c3b49 100644 --- a/validator-api/README.md +++ b/nym-api/README.md @@ -13,7 +13,13 @@ The Nym directory is contained in the mixnet smart contract in the Nyx blockchai Nym Network Monitoring ----------------------- -The Nym API periodically sends test packets through the entire of the Nym mixnet to test node liveness and quality of service. +The Nym API periodically sends test packets through the entire Nym mixnet to test node liveness and quality of service. + + +Nym Epoch Advancement and Payment +--------------------------------- + +The Nym API periodically advances the epoch and triggers payment based on network monitoring measurements. Coconut Credentials diff --git a/validator-api/Rocket.toml b/nym-api/Rocket.toml similarity index 100% rename from validator-api/Rocket.toml rename to nym-api/Rocket.toml diff --git a/validator-api/build.rs b/nym-api/build.rs similarity index 81% rename from validator-api/build.rs rename to nym-api/build.rs index 1e967f0aab..cdd97f9505 100644 --- a/validator-api/build.rs +++ b/nym-api/build.rs @@ -1,11 +1,10 @@ use sqlx::{Connection, SqliteConnection}; use std::env; -use vergen::{vergen, Config}; #[tokio::main] async fn main() { let out_dir = env::var("OUT_DIR").unwrap(); - let database_path = format!("{}/validator-api-example.sqlite", out_dir); + let database_path = format!("{}/nym-api-example.sqlite", out_dir); let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path)) .await @@ -23,6 +22,4 @@ async fn main() { // 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); - - vergen(Config::default()).expect("failed to extract build metadata") } diff --git a/validator-api/migrations/20210722120000_create_initial_tables.sql b/nym-api/migrations/20210722120000_create_initial_tables.sql similarity index 100% rename from validator-api/migrations/20210722120000_create_initial_tables.sql rename to nym-api/migrations/20210722120000_create_initial_tables.sql diff --git a/validator-api/migrations/20210819120000_monitor_runs.sql b/nym-api/migrations/20210819120000_monitor_runs.sql similarity index 100% rename from validator-api/migrations/20210819120000_monitor_runs.sql rename to nym-api/migrations/20210819120000_monitor_runs.sql diff --git a/validator-api/migrations/20210824120000_rewarding_data.sql b/nym-api/migrations/20210824120000_rewarding_data.sql similarity index 100% rename from validator-api/migrations/20210824120000_rewarding_data.sql rename to nym-api/migrations/20210824120000_rewarding_data.sql diff --git a/validator-api/migrations/20211018120000_core_nodes_testing.sql b/nym-api/migrations/20211018120000_core_nodes_testing.sql similarity index 100% rename from validator-api/migrations/20211018120000_core_nodes_testing.sql rename to nym-api/migrations/20211018120000_core_nodes_testing.sql diff --git a/validator-api/migrations/20211101110000_no_gateway_rewarding.sql b/nym-api/migrations/20211101110000_no_gateway_rewarding.sql similarity index 100% rename from validator-api/migrations/20211101110000_no_gateway_rewarding.sql rename to nym-api/migrations/20211101110000_no_gateway_rewarding.sql diff --git a/validator-api/migrations/20220121120000_epoch_interval_renaming.sql b/nym-api/migrations/20220121120000_epoch_interval_renaming.sql similarity index 100% rename from validator-api/migrations/20220121120000_epoch_interval_renaming.sql rename to nym-api/migrations/20220121120000_epoch_interval_renaming.sql diff --git a/validator-api/migrations/20220323120000_drop_foreign_keys_constraints.sql b/nym-api/migrations/20220323120000_drop_foreign_keys_constraints.sql similarity index 100% rename from validator-api/migrations/20220323120000_drop_foreign_keys_constraints.sql rename to nym-api/migrations/20220323120000_drop_foreign_keys_constraints.sql diff --git a/validator-api/migrations/20220325120000_signed_deposits.sql b/nym-api/migrations/20220325120000_signed_deposits.sql similarity index 100% rename from validator-api/migrations/20220325120000_signed_deposits.sql rename to nym-api/migrations/20220325120000_signed_deposits.sql diff --git a/validator-api/migrations/20220410222400_add_timestamp_indexes.sql b/nym-api/migrations/20220410222400_add_timestamp_indexes.sql similarity index 100% rename from validator-api/migrations/20220410222400_add_timestamp_indexes.sql rename to nym-api/migrations/20220410222400_add_timestamp_indexes.sql diff --git a/validator-api/migrations/20220804120000_mixnet_contract_v2.sql b/nym-api/migrations/20220804120000_mixnet_contract_v2.sql similarity index 100% rename from validator-api/migrations/20220804120000_mixnet_contract_v2.sql rename to nym-api/migrations/20220804120000_mixnet_contract_v2.sql diff --git a/validator-api/validator-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml similarity index 62% rename from validator-api/validator-api-requests/Cargo.toml rename to nym-api/nym-api-requests/Cargo.toml index ecd29e78a4..ed8dc1dce6 100644 --- a/validator-api/validator-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "validator-api-requests" +name = "nym-api-requests" version = "0.1.0" edition = "2021" @@ -8,16 +8,15 @@ edition = "2021" [dependencies] bs58 = "0.4.0" cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } -cosmwasm-std = { version = "1.0.0", default-features = false } +cosmwasm-std = { workspace = true, default-features = false } getset = "0.1.1" schemars = { version = "0.8", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } ts-rs = { version = "6.1.2", optional = true } -coconut-interface = { path = "../../common/coconut-interface", optional = true } -mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-coconut-interface = { path = "../../common/coconut-interface" } +nym-mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" } [features] default = [] -coconut = ["coconut-interface"] generate-ts = ["ts-rs"] diff --git a/validator-api/validator-api-requests/src/coconut.rs b/nym-api/nym-api-requests/src/coconut.rs similarity index 99% rename from validator-api/validator-api-requests/src/coconut.rs rename to nym-api/nym-api-requests/src/coconut.rs index 18afdd81e9..6f79bc2f21 100644 --- a/validator-api/validator-api-requests/src/coconut.rs +++ b/nym-api/nym-api-requests/src/coconut.rs @@ -5,7 +5,7 @@ use cosmrs::AccountId; use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; -use coconut_interface::{ +use nym_coconut_interface::{ error::CoconutInterfaceError, Attribute, Base58, BlindSignRequest, Credential, VerificationKey, }; diff --git a/validator-api/validator-api-requests/src/lib.rs b/nym-api/nym-api-requests/src/lib.rs similarity index 96% rename from validator-api/validator-api-requests/src/lib.rs rename to nym-api/nym-api-requests/src/lib.rs index 7f211067ca..cb0bc32f63 100644 --- a/validator-api/validator-api-requests/src/lib.rs +++ b/nym-api/nym-api-requests/src/lib.rs @@ -4,7 +4,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[cfg(feature = "coconut")] pub mod coconut; pub mod models; diff --git a/validator-api/validator-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs similarity index 79% rename from validator-api/validator-api-requests/src/models.rs rename to nym-api/nym-api-requests/src/models.rs index eee713a701..319d49edd1 100644 --- a/validator-api/validator-api-requests/src/models.rs +++ b/nym-api/nym-api-requests/src/models.rs @@ -1,13 +1,13 @@ // Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use cosmwasm_std::{Coin, Decimal}; -use mixnet_contract_common::families::FamilyHead; -use mixnet_contract_common::mixnode::MixNodeDetails; -use mixnet_contract_common::reward_params::{Performance, RewardingParams}; -use mixnet_contract_common::rewarding::RewardEstimate; -use mixnet_contract_common::{ - IdentityKey, Interval, MixId, MixNode, Percent, RewardedSetNodeStatus, +use cosmwasm_std::{Addr, Coin, Decimal}; +use nym_mixnet_contract_common::families::FamilyHead; +use nym_mixnet_contract_common::mixnode::MixNodeDetails; +use nym_mixnet_contract_common::reward_params::{Performance, RewardingParams}; +use nym_mixnet_contract_common::rewarding::RewardEstimate; +use nym_mixnet_contract_common::{ + GatewayBond, IdentityKey, Interval, MixId, MixNode, Percent, RewardedSetNodeStatus, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -93,15 +93,25 @@ pub struct MixnodeStatusResponse { pub status: MixnodeStatus, } +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct NodePerformance { + pub most_recent: Performance, + pub last_hour: Performance, + pub last_24h: Performance, +} + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct MixNodeBondAnnotated { pub mixnode_details: MixNodeDetails, pub stake_saturation: StakeSaturation, pub uncapped_stake_saturation: StakeSaturation, + // NOTE: the performance field is deprecated in favour of node_performance pub performance: Performance, + pub node_performance: NodePerformance, pub estimated_operator_apy: Decimal, pub estimated_delegators_apy: Decimal, pub family: Option<FamilyHead>, + pub blacklisted: bool, } impl MixNodeBondAnnotated { @@ -112,9 +122,36 @@ impl MixNodeBondAnnotated { pub fn mix_id(&self) -> MixId { self.mixnode_details.mix_id() } + + pub fn identity_key(&self) -> &str { + self.mixnode_details.bond_information.identity() + } + + pub fn owner(&self) -> &Addr { + self.mixnode_details.bond_information.owner() + } } -#[derive(Deserialize, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct GatewayBondAnnotated { + pub gateway_bond: GatewayBond, + // NOTE: the performance field is deprecated in favour of node_performance + pub performance: Performance, + pub node_performance: NodePerformance, + pub blacklisted: bool, +} + +impl GatewayBondAnnotated { + pub fn identity(&self) -> &String { + self.gateway_bond.identity() + } + + pub fn owner(&self) -> &Addr { + self.gateway_bond.owner() + } +} + +#[derive(Debug, Serialize, Deserialize, JsonSchema)] pub struct ComputeRewardEstParam { pub performance: Option<Performance>, pub active_in_rewarded_set: Option<bool>, @@ -141,7 +178,17 @@ pub struct RewardEstimationResponse { #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct UptimeResponse { pub mix_id: MixId, + // The same as node_performance.last_24h. Legacy pub avg_uptime: u8, + pub node_performance: NodePerformance, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct GatewayUptimeResponse { + pub identity: String, + // The same as node_performance.last_24h. Legacy + pub avg_uptime: u8, + pub node_performance: NodePerformance, } #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)] @@ -282,3 +329,11 @@ pub struct GatewayUptimeHistoryResponse { pub owner: String, pub history: Vec<HistoricalUptimeResponse>, } + +#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)] +pub struct CirculatingSupplyResponse { + pub total_supply: Coin, + pub mixmining_reserve: Coin, + pub vesting_tokens: Coin, + pub circulating_supply: Coin, +} diff --git a/nym-api/src/circulating_supply_api/cache/data.rs b/nym-api/src/circulating_supply_api/cache/data.rs new file mode 100644 index 0000000000..17e906dff3 --- /dev/null +++ b/nym-api/src/circulating_supply_api/cache/data.rs @@ -0,0 +1,38 @@ +// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::support::caching::Cache; +use nym_api_requests::models::CirculatingSupplyResponse; +use validator_client::nyxd::Coin; + +pub(crate) struct CirculatingSupplyCacheData { + // no need to cache that one as it's constant, but let's put it here for consistency sake + pub(crate) total_supply: Coin, + pub(crate) mixmining_reserve: Cache<Coin>, + pub(crate) vesting_tokens: Cache<Coin>, + pub(crate) circulating_supply: Cache<Coin>, +} + +impl CirculatingSupplyCacheData { + pub fn new(mix_denom: String) -> CirculatingSupplyCacheData { + let zero_coin = Coin::new(0, &mix_denom); + + CirculatingSupplyCacheData { + total_supply: Coin::new(1_000_000_000_000_000, mix_denom), + mixmining_reserve: Cache::new(zero_coin.clone()), + vesting_tokens: Cache::new(zero_coin.clone()), + circulating_supply: Cache::new(zero_coin), + } + } +} + +impl<'a> From<&'a CirculatingSupplyCacheData> for CirculatingSupplyResponse { + fn from(value: &'a CirculatingSupplyCacheData) -> Self { + CirculatingSupplyResponse { + total_supply: value.total_supply.clone().into(), + mixmining_reserve: value.mixmining_reserve.clone().into_inner().into(), + vesting_tokens: value.vesting_tokens.clone().into_inner().into(), + circulating_supply: value.circulating_supply.clone().into_inner().into(), + } + } +} diff --git a/nym-api/src/circulating_supply_api/cache/mod.rs b/nym-api/src/circulating_supply_api/cache/mod.rs new file mode 100644 index 0000000000..2f432c8d4d --- /dev/null +++ b/nym-api/src/circulating_supply_api/cache/mod.rs @@ -0,0 +1,92 @@ +// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use self::data::CirculatingSupplyCacheData; +use cosmwasm_std::Addr; +use nym_api_requests::models::CirculatingSupplyResponse; +use rocket::fairing::AdHoc; +use std::ops::Deref; +use std::{ + sync::{atomic::AtomicBool, Arc}, + time::Duration, +}; +use thiserror::Error; +use tokio::sync::RwLock; +use tokio::time; +use validator_client::nyxd::Coin; +use validator_client::ValidatorClientError; + +mod data; +pub(crate) mod refresher; + +#[derive(Debug, Error)] +enum CirculatingSupplyCacheError { + // this can only happen if somebody decides to set their staking address + // before https://github.com/nymtech/nym/pull/2796 is deployed + #[error("vesting account owned by {owner} with id {account_id} appeared more than once in the query response")] + DuplicateVestingAccountEntry { owner: Addr, account_id: u32 }, + + // this can happen if somehow the query was incomplete, like some paged sub-query didn't return full result + // or there's a bug with paging. or if, somehow, a vesting account got removed from the contract + #[error("got an inconsistent number of vesting account. received data on {got}, but expected {expected}")] + InconsistentNumberOfVestingAccounts { expected: usize, got: usize }, + + #[error(transparent)] + ClientError { + #[from] + source: ValidatorClientError, + }, +} + +/// A cache for the circulating supply of the network. Circulating supply is calculated by +/// taking the initial supply of 1bn coins, and subtracting the amount of coins that are +/// in the mixmining pool and tied up in vesting. +/// +/// The cache is quite simple and does not include an update listener that the other caches have. +#[derive(Clone)] +pub(crate) struct CirculatingSupplyCache { + initialised: Arc<AtomicBool>, + data: Arc<RwLock<CirculatingSupplyCacheData>>, +} + +impl CirculatingSupplyCache { + fn new(mix_denom: String) -> CirculatingSupplyCache { + CirculatingSupplyCache { + initialised: Arc::new(AtomicBool::new(false)), + data: Arc::new(RwLock::new(CirculatingSupplyCacheData::new(mix_denom))), + } + } + + pub(crate) async fn get_circulating_supply(&self) -> Option<CirculatingSupplyResponse> { + match time::timeout(Duration::from_millis(100), self.data.read()).await { + Ok(cache) => Some(cache.deref().into()), + Err(err) => { + error!("Failed to get circulating supply: {err}"); + None + } + } + } + + pub(crate) fn stage(mix_denom: String) -> AdHoc { + AdHoc::on_ignite("Circulating Supply Cache Stage", |rocket| async { + rocket.manage(Self::new(mix_denom)) + }) + } + + pub(crate) async fn update(&self, mixmining_reserve: Coin, vesting_tokens: Coin) { + let mut cache = self.data.write().await; + + let mut circulating_supply = cache.total_supply.clone(); + circulating_supply.amount -= mixmining_reserve.amount; + circulating_supply.amount -= vesting_tokens.amount; + + log::info!("Updating circulating supply cache"); + log::info!("the mixmining reserve is now {mixmining_reserve}"); + log::info!("the number of tokens still vesting is now {vesting_tokens}"); + log::info!("the circulating supply is now {circulating_supply}"); + + cache.mixmining_reserve.update(mixmining_reserve); + cache.vesting_tokens.update(vesting_tokens); + cache.circulating_supply.update(circulating_supply); + } +} diff --git a/nym-api/src/circulating_supply_api/cache/refresher.rs b/nym-api/src/circulating_supply_api/cache/refresher.rs new file mode 100644 index 0000000000..c28859eb15 --- /dev/null +++ b/nym-api/src/circulating_supply_api/cache/refresher.rs @@ -0,0 +1,134 @@ +// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use super::CirculatingSupplyCache; +use crate::circulating_supply_api::cache::CirculatingSupplyCacheError; +use crate::support::nyxd::Client; +use nym_contracts_common::truncate_decimal; +use nym_task::TaskClient; +use std::collections::HashSet; +use std::sync::atomic::Ordering; +use std::time::Duration; +use tokio::time; +use validator_client::nyxd::Coin; + +pub(crate) struct CirculatingSupplyCacheRefresher { + nyxd_client: Client, + cache: CirculatingSupplyCache, + caching_interval: Duration, +} + +impl CirculatingSupplyCacheRefresher { + pub(crate) fn new( + nyxd_client: Client, + cache: CirculatingSupplyCache, + caching_interval: Duration, + ) -> Self { + CirculatingSupplyCacheRefresher { + nyxd_client, + cache, + caching_interval, + } + } + + pub(crate) async fn run(&self, mut shutdown: TaskClient) { + let mut interval = time::interval(self.caching_interval); + while !shutdown.is_shutdown() { + tokio::select! { + _ = interval.tick() => { + tokio::select! { + biased; + _ = shutdown.recv() => { + trace!("CirculatingSupplyCacheRefresher: Received shutdown"); + } + ret = self.refresh() => { + if let Err(err) = ret { + error!("Failed to refresh circulating supply cache - {err}"); + } else { + // relaxed memory ordering is fine here. worst case scenario network monitor + // will just have to wait for an additional backoff to see the change. + // And so this will not really incur any performance penalties by setting it every loop iteration + self.cache.initialised.store(true, Ordering::Relaxed) + } + } + } + } + _ = shutdown.recv() => { + trace!("CirculatingSupplyCacheRefresher: Received shutdown"); + } + } + } + } + + async fn get_mixmining_reserve( + &self, + mix_denom: &str, + ) -> Result<Coin, CirculatingSupplyCacheError> { + let reward_pool = self + .nyxd_client + .get_current_rewarding_parameters() + .await? + .interval + .reward_pool; + + Ok(Coin::new(truncate_decimal(reward_pool).u128(), mix_denom)) + } + + async fn get_total_vesting_tokens( + &self, + mix_denom: &str, + ) -> Result<Coin, CirculatingSupplyCacheError> { + let all_vesting = self.nyxd_client.get_all_vesting_coins().await?; + + // sanity check invariants to make sure all accounts got considered and we got no duplicates + // the cache refreshes so infrequently that the performance penalty is negligible + let mut owners = HashSet::new(); + let mut ids = HashSet::new(); + for acc in &all_vesting { + if !owners.insert(acc.owner.clone()) { + return Err(CirculatingSupplyCacheError::DuplicateVestingAccountEntry { + owner: acc.owner.clone(), + account_id: acc.account_id, + }); + } + + if !ids.insert(acc.account_id) { + return Err(CirculatingSupplyCacheError::DuplicateVestingAccountEntry { + owner: acc.owner.clone(), + account_id: acc.account_id, + }); + } + } + + let current_storage_key = self + .nyxd_client + .get_current_vesting_account_storage_key() + .await?; + if all_vesting.len() != current_storage_key as usize { + return Err( + CirculatingSupplyCacheError::InconsistentNumberOfVestingAccounts { + expected: current_storage_key as usize, + got: all_vesting.len(), + }, + ); + } + + let mut total = Coin::new(0, mix_denom); + for account in all_vesting { + total.amount += account.still_vesting.amount.u128(); + } + + Ok(total) + } + + async fn refresh(&self) -> Result<(), CirculatingSupplyCacheError> { + let chain_details = self.nyxd_client.chain_details().await; + let mix_denom = &chain_details.mix_denom.base; + + let mixmining_reserve = self.get_mixmining_reserve(mix_denom).await?; + let vesting_tokens = self.get_total_vesting_tokens(mix_denom).await?; + + self.cache.update(mixmining_reserve, vesting_tokens).await; + Ok(()) + } +} diff --git a/nym-api/src/circulating_supply_api/mod.rs b/nym-api/src/circulating_supply_api/mod.rs new file mode 100644 index 0000000000..39dfe28edc --- /dev/null +++ b/nym-api/src/circulating_supply_api/mod.rs @@ -0,0 +1,41 @@ +// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use nym_task::TaskManager; +use okapi::openapi3::OpenApi; +use rocket::Route; +use rocket_okapi::{openapi_get_routes_spec, settings::OpenApiSettings}; + +use crate::support::{config::Config, nyxd}; + +use self::cache::refresher::CirculatingSupplyCacheRefresher; + +pub(crate) mod cache; +pub(crate) mod routes; + +/// Merges the routes with http information and returns it to Rocket for serving +pub(crate) fn circulating_supply_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) { + openapi_get_routes_spec![ + settings: routes::get_full_circulating_supply, + routes::get_total_supply, + routes::get_circulating_supply + ] +} + +/// Spawn the circulating supply cache refresher. +pub(crate) fn start_cache_refresh( + config: &Config, + nyxd_client: nyxd::Client, + circulating_supply_cache: &cache::CirculatingSupplyCache, + shutdown: &TaskManager, +) { + if config.get_circulating_supply_enabled() { + let refresher = CirculatingSupplyCacheRefresher::new( + nyxd_client, + circulating_supply_cache.to_owned(), + config.get_circulating_supply_caching_interval(), + ); + let shutdown_listener = shutdown.subscribe(); + tokio::spawn(async move { refresher.run(shutdown_listener).await }); + } +} diff --git a/nym-api/src/circulating_supply_api/routes.rs b/nym-api/src/circulating_supply_api/routes.rs new file mode 100644 index 0000000000..da6aea1d22 --- /dev/null +++ b/nym-api/src/circulating_supply_api/routes.rs @@ -0,0 +1,77 @@ +// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::circulating_supply_api::cache::CirculatingSupplyCache; +use crate::node_status_api::models::ErrorResponse; +use nym_api_requests::models::CirculatingSupplyResponse; +use rocket::http::Status; +use rocket::serde::json::Json; +use rocket::State; +use rocket_okapi::openapi; +use validator_client::nyxd::Coin; + +// TODO: this is not the best place to put it, it should be more centralised, +// but for a quick fix, that's good enough for now... +// (for proper solution we should be managing `NymNetworkDetails` via rocket and grabbing display exponent +// value from the mix denom here. +const UNYM_RATIO: f64 = 1000000.; + +fn unym_coin_to_float_unym(coin: Coin) -> f64 { + // our total supply can't exceed 1B so an overflow here is impossible + // (if it happened, then we SHOULD crash) + coin.amount as f64 / UNYM_RATIO +} + +#[openapi(tag = "circulating-supply")] +#[get("/circulating-supply")] +pub(crate) async fn get_full_circulating_supply( + cache: &State<CirculatingSupplyCache>, +) -> Result<Json<CirculatingSupplyResponse>, ErrorResponse> { + match cache.get_circulating_supply().await { + Some(value) => Ok(Json(value)), + None => Err(ErrorResponse::new( + "unavailable", + Status::InternalServerError, + )), + } +} + +#[openapi(tag = "circulating-supply")] +#[get("/circulating-supply/total-supply-value")] +pub(crate) async fn get_total_supply( + cache: &State<CirculatingSupplyCache>, +) -> Result<Json<f64>, ErrorResponse> { + let full_circulating_supply = match cache.get_circulating_supply().await { + Some(res) => res, + None => { + return Err(ErrorResponse::new( + "unavailable", + Status::InternalServerError, + )) + } + }; + + Ok(Json(unym_coin_to_float_unym( + full_circulating_supply.total_supply.into(), + ))) +} + +#[openapi(tag = "circulating-supply")] +#[get("/circulating-supply/circulating-supply-value")] +pub(crate) async fn get_circulating_supply( + cache: &State<CirculatingSupplyCache>, +) -> Result<Json<f64>, ErrorResponse> { + let full_circulating_supply = match cache.get_circulating_supply().await { + Some(res) => res, + None => { + return Err(ErrorResponse::new( + "unavailable", + Status::InternalServerError, + )) + } + }; + + Ok(Json(unym_coin_to_float_unym( + full_circulating_supply.circulating_supply.into(), + ))) +} diff --git a/validator-api/src/coconut/client.rs b/nym-api/src/coconut/client.rs similarity index 54% rename from validator-api/src/coconut/client.rs rename to nym-api/src/coconut/client.rs index 87a64a4e6e..3ada819048 100644 --- a/validator-api/src/coconut/client.rs +++ b/nym-api/src/coconut/client.rs @@ -2,15 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 use crate::coconut::error::Result; -use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; -use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; -use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; -use contracts_common::dealings::ContractSafeBytes; use cw3::ProposalResponse; -use dkg::Threshold; -use validator_client::nymd::cosmwasm_client::types::ExecuteResult; -use validator_client::nymd::{AccountId, Fee, TxResponse}; +use cw4::MemberResponse; +use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; +use nym_coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; +use nym_coconut_dkg_common::types::{ + EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, +}; +use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; +use nym_contracts_common::dealings::ContractSafeBytes; +use nym_dkg::Threshold; +use validator_client::nyxd::cosmwasm_client::types::ExecuteResult; +use validator_client::nyxd::{AccountId, Fee, TxResponse}; #[async_trait] pub trait Client { @@ -22,23 +25,32 @@ pub trait Client { &self, blinded_serial_number: String, ) -> Result<SpendCredentialResponse>; - async fn get_current_epoch_state(&self) -> Result<EpochState>; + async fn get_current_epoch(&self) -> Result<Epoch>; + async fn group_member(&self, addr: String) -> Result<MemberResponse>; async fn get_current_epoch_threshold(&self) -> Result<Option<Threshold>>; + async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>>; async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse>; async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>>; async fn get_dealings(&self, idx: usize) -> Result<Vec<ContractDealing>>; - async fn get_verification_key_shares(&self) -> Result<Vec<ContractVKShare>>; + async fn get_verification_key_shares(&self, epoch_id: EpochId) -> Result<Vec<ContractVKShare>>; async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option<Fee>) -> Result<()>; async fn execute_proposal(&self, proposal_id: u64) -> Result<()>; + async fn advance_epoch_state(&self) -> Result<()>; async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, announce_address: String, + resharing: bool, + ) -> Result<ExecuteResult>; + async fn submit_dealing( + &self, + dealing_bytes: ContractSafeBytes, + resharing: bool, ) -> Result<ExecuteResult>; - async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result<ExecuteResult>; async fn submit_verification_key_share( &self, share: VerificationKeyShare, + resharing: bool, ) -> Result<ExecuteResult>; } diff --git a/nym-api/src/coconut/comm.rs b/nym-api/src/coconut/comm.rs new file mode 100644 index 0000000000..39fb970a02 --- /dev/null +++ b/nym-api/src/coconut/comm.rs @@ -0,0 +1,35 @@ +// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::error::Result; +use crate::nyxd; +use nym_coconut_dkg_common::types::EpochId; +use nym_coconut_interface::VerificationKey; +use nym_credentials::coconut::utils::obtain_aggregate_verification_key; +use validator_client::CoconutApiClient; + +#[async_trait] +pub trait APICommunicationChannel { + async fn aggregated_verification_key(&self, epoch_id: EpochId) -> Result<VerificationKey>; +} + +pub(crate) struct QueryCommunicationChannel { + nyxd_client: nyxd::Client, +} + +impl QueryCommunicationChannel { + pub fn new(nyxd_client: nyxd::Client) -> Self { + QueryCommunicationChannel { nyxd_client } + } +} + +#[async_trait] +impl APICommunicationChannel for QueryCommunicationChannel { + async fn aggregated_verification_key(&self, epoch_id: EpochId) -> Result<VerificationKey> { + let client = self.nyxd_client.0.read().await; + let coconut_api_clients = + CoconutApiClient::all_coconut_api_clients(&client, epoch_id).await?; + let vk = obtain_aggregate_verification_key(&coconut_api_clients).await?; + Ok(vk) + } +} diff --git a/validator-api/src/coconut/deposit.rs b/nym-api/src/coconut/deposit.rs similarity index 90% rename from validator-api/src/coconut/deposit.rs rename to nym-api/src/coconut/deposit.rs index 8dba92a705..4d3eab394f 100644 --- a/validator-api/src/coconut/deposit.rs +++ b/nym-api/src/coconut/deposit.rs @@ -1,15 +1,15 @@ // Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use coconut_bandwidth_contract_common::events::{ +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, }; -use credentials::coconut::bandwidth::BandwidthVoucher; -use crypto::asymmetric::encryption; -use crypto::asymmetric::identity::{self, Signature}; -use validator_api_requests::coconut::BlindSignRequestBody; -use validator_client::nymd::TxResponse; +use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_crypto::asymmetric::encryption; +use nym_crypto::asymmetric::identity::{self, Signature}; +use validator_client::nyxd::TxResponse; use super::error::{CoconutError, Result}; @@ -95,12 +95,12 @@ pub async fn extract_encryption_key( mod test { use super::*; use crate::coconut::tests::tx_entry_fixture; - use config::defaults::VOUCHER_INFO; + use nym_config::defaults::VOUCHER_INFO; use nymcoconut::{prepare_blind_sign, BlindSignRequest, Parameters}; use rand_07::rngs::OsRng; use std::str::FromStr; - use validator_client::nymd::tx::Hash; - use validator_client::nymd::{Event, Tag}; + use validator_client::nyxd::tx::Hash; + use validator_client::nyxd::{Event, Tag}; #[tokio::test] async fn extract_encryption_key_test() { @@ -164,17 +164,13 @@ mod test { let err = extract_encryption_key(&req, tx_entry.clone()) .await .unwrap_err(); - assert_eq!( - err.to_string(), + + assert!(matches!( + err, CoconutError::Ed25519ParseError( - // this is really just a useless, dummy error value needed to generate the error type - // and get its string representation - crypto::asymmetric::identity::Ed25519RecoveryError::MalformedBytes( - crypto::asymmetric::identity::SignatureError::new(), - ), + nym_crypto::asymmetric::identity::Ed25519RecoveryError::MalformedSignatureString { .. } ) - .to_string() - ); + )); let correct_request = BlindSignRequestBody::new( &blind_sign_req, @@ -279,17 +275,13 @@ mod test { let err = extract_encryption_key(&correct_request, tx_entry.clone()) .await .unwrap_err(); - assert_eq!( - err.to_string(), + + assert!(matches!( + err, CoconutError::Ed25519ParseError( - // this is really just a useless, dummy error value needed to generate the error type - // and get its string representation - crypto::asymmetric::identity::Ed25519RecoveryError::MalformedBytes( - crypto::asymmetric::identity::SignatureError::new(), - ), + nym_crypto::asymmetric::identity::Ed25519RecoveryError::MalformedPublicKeyString { .. } ) - .to_string(), - ); + )); tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ Tag { @@ -338,15 +330,13 @@ mod test { let err = extract_encryption_key(&correct_request, tx_entry.clone()) .await .unwrap_err(); - assert_eq!( - err.to_string(), + + assert!(matches!( + err, CoconutError::X25519ParseError( - // this is really just a useless, dummy error value needed to generate the error type - // and get its string representation - crypto::asymmetric::encryption::KeyRecoveryError::InvalidPublicKeyBytes, + nym_crypto::asymmetric::encryption::KeyRecoveryError::MalformedPublicKeyString { .. } ) - .to_string(), - ); + )); let expected_encryption_key = "HxnTpWTkgigSTAysVKLE8pEiUULHdTT1BxFfzfJvQRi6"; tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ @@ -375,7 +365,7 @@ mod test { assert_eq!( err.to_string(), CoconutError::SignatureVerificationError( - crypto::asymmetric::identity::SignatureError::default(), + nym_crypto::asymmetric::identity::SignatureError::default(), ) .to_string(), ); diff --git a/validator-api/src/coconut/dkg/client.rs b/nym-api/src/coconut/dkg/client.rs similarity index 65% rename from validator-api/src/coconut/dkg/client.rs rename to nym-api/src/coconut/dkg/client.rs index d8a0cf5d69..8064ca5a7b 100644 --- a/validator-api/src/coconut/dkg/client.rs +++ b/nym-api/src/coconut/dkg/client.rs @@ -3,15 +3,18 @@ use crate::coconut::client::Client; use crate::coconut::error::CoconutError; -use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState, NodeIndex}; -use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; -use contracts_common::dealings::ContractSafeBytes; use cw3::ProposalResponse; -use dkg::Threshold; -use validator_client::nymd::cosmwasm_client::logs::{find_attribute, NODE_INDEX}; -use validator_client::nymd::cosmwasm_client::types::ExecuteResult; -use validator_client::nymd::AccountId; +use cw4::MemberResponse; +use nym_coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; +use nym_coconut_dkg_common::types::{ + EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, NodeIndex, +}; +use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; +use nym_contracts_common::dealings::ContractSafeBytes; +use nym_dkg::Threshold; +use validator_client::nyxd::cosmwasm_client::logs::{find_attribute, NODE_INDEX}; +use validator_client::nyxd::cosmwasm_client::types::ExecuteResult; +use validator_client::nyxd::AccountId; pub(crate) struct DkgClient { inner: Box<dyn Client + Send + Sync>, @@ -22,36 +25,48 @@ impl DkgClient { // Until we determine why that is, retry the query a few more times const RETRIES: usize = 3; - pub(crate) fn new<C>(nymd_client: C) -> Self + pub(crate) fn new<C>(nyxd_client: C) -> Self where C: Client + Send + Sync + 'static, { DkgClient { - inner: Box::new(nymd_client), + inner: Box::new(nyxd_client), } } - pub(crate) async fn _get_address(&self) -> AccountId { + pub(crate) async fn get_address(&self) -> AccountId { self.inner.address().await } - pub(crate) async fn get_current_epoch_state(&self) -> Result<EpochState, CoconutError> { - let mut ret = self.inner.get_current_epoch_state().await; + pub(crate) async fn get_current_epoch(&self) -> Result<Epoch, CoconutError> { + let mut ret = self.inner.get_current_epoch().await; for _ in 0..Self::RETRIES { if ret.is_ok() { return ret; } - ret = self.inner.get_current_epoch_state().await; + ret = self.inner.get_current_epoch().await; } ret } + pub(crate) async fn group_member(&self) -> Result<MemberResponse, CoconutError> { + self.inner + .group_member(self.get_address().await.to_string()) + .await + } + pub(crate) async fn get_current_epoch_threshold( &self, ) -> Result<Option<Threshold>, CoconutError> { self.inner.get_current_epoch_threshold().await } + pub(crate) async fn get_initial_dealers( + &self, + ) -> Result<Option<InitialReplacementData>, CoconutError> { + self.inner.get_initial_dealers().await + } + pub(crate) async fn get_self_registered_dealer_details( &self, ) -> Result<DealerDetailsResponse, CoconutError> { @@ -78,22 +93,28 @@ impl DkgClient { pub(crate) async fn get_verification_key_shares( &self, + epoch_id: EpochId, ) -> Result<Vec<ContractVKShare>, CoconutError> { - self.inner.get_verification_key_shares().await + self.inner.get_verification_key_shares(epoch_id).await } pub(crate) async fn list_proposals(&self) -> Result<Vec<ProposalResponse>, CoconutError> { self.inner.list_proposals().await } + pub(crate) async fn advance_epoch_state(&self) -> Result<(), CoconutError> { + self.inner.advance_epoch_state().await + } + pub(crate) async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, announce_address: String, + resharing: bool, ) -> Result<NodeIndex, CoconutError> { let res = self .inner - .register_dealer(bte_key, announce_address) + .register_dealer(bte_key, announce_address, resharing) .await?; let node_index = find_attribute(&res.logs, "wasm", NODE_INDEX) .ok_or(CoconutError::NodeIndexRecoveryError { @@ -111,18 +132,20 @@ impl DkgClient { pub(crate) async fn submit_dealing( &self, dealing_bytes: ContractSafeBytes, + resharing: bool, ) -> Result<(), CoconutError> { - self.inner.submit_dealing(dealing_bytes).await?; + self.inner.submit_dealing(dealing_bytes, resharing).await?; Ok(()) } pub(crate) async fn submit_verification_key_share( &self, share: VerificationKeyShare, + resharing: bool, ) -> Result<ExecuteResult, CoconutError> { let mut ret = self .inner - .submit_verification_key_share(share.clone()) + .submit_verification_key_share(share.clone(), resharing) .await; for _ in 0..Self::RETRIES { if let Ok(res) = ret { @@ -130,7 +153,7 @@ impl DkgClient { } ret = self .inner - .submit_verification_key_share(share.clone()) + .submit_verification_key_share(share.clone(), resharing) .await; } ret diff --git a/validator-api/src/coconut/dkg/complaints.rs b/nym-api/src/coconut/dkg/complaints.rs similarity index 100% rename from validator-api/src/coconut/dkg/complaints.rs rename to nym-api/src/coconut/dkg/complaints.rs diff --git a/nym-api/src/coconut/dkg/controller.rs b/nym-api/src/coconut/dkg/controller.rs new file mode 100644 index 0000000000..b4fc713741 --- /dev/null +++ b/nym-api/src/coconut/dkg/controller.rs @@ -0,0 +1,197 @@ +// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::dkg::client::DkgClient; +use crate::coconut::dkg::state::{ConsistentState, PersistentState, State}; +use crate::coconut::dkg::verification_key::{ + verification_key_finalization, verification_key_validation, +}; +use crate::coconut::dkg::{ + dealing::dealing_exchange, public_key::public_key_submission, + verification_key::verification_key_submission, +}; +use crate::coconut::keypair::KeyPair as CoconutKeyPair; +use crate::nyxd; +use crate::support::config::Config; +use anyhow::Result; +use nym_coconut_dkg_common::types::EpochState; +use nym_dkg::bte::keys::KeyPair as DkgKeyPair; +use nym_task::{TaskClient, TaskManager}; +use rand::rngs::OsRng; +use rand::{CryptoRng, RngCore}; +use std::path::PathBuf; +use std::time::{Duration, SystemTime}; +use tokio::time::interval; + +pub(crate) fn init_keypair(config: &Config) -> Result<()> { + let mut rng = OsRng; + let dkg_params = nym_dkg::bte::setup(); + let kp = DkgKeyPair::new(&dkg_params, &mut rng); + nym_pemstore::store_keypair( + &kp, + &nym_pemstore::KeyPairPath::new( + config.decryption_key_path(), + config.public_key_with_proof_path(), + ), + )?; + Ok(()) +} + +pub(crate) struct DkgController<R> { + dkg_client: DkgClient, + secret_key_path: PathBuf, + verification_key_path: PathBuf, + state: State, + rng: R, + polling_rate: Duration, +} + +impl<R: RngCore + CryptoRng + Clone> DkgController<R> { + pub(crate) async fn new( + config: &Config, + nyxd_client: nyxd::Client, + coconut_keypair: CoconutKeyPair, + rng: R, + ) -> Result<Self> { + let dkg_keypair = nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( + config.decryption_key_path(), + config.public_key_with_proof_path(), + ))?; + if let Ok(coconut_keypair_value) = + nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( + config.secret_key_path(), + config.verification_key_path(), + )) + { + coconut_keypair.set(Some(coconut_keypair_value)).await; + } + let persistent_state = + PersistentState::load_from_file(config.persistent_state_path()).unwrap_or_default(); + + Ok(DkgController { + dkg_client: DkgClient::new(nyxd_client), + secret_key_path: config.secret_key_path(), + verification_key_path: config.verification_key_path(), + state: State::new( + config.persistent_state_path(), + persistent_state, + config.get_announce_address(), + dkg_keypair, + coconut_keypair, + ), + rng, + polling_rate: config.get_dkg_contract_polling_rate(), + }) + } + + pub(crate) async fn handle_epoch_state(&mut self) { + match self.dkg_client.get_current_epoch().await { + Err(err) => warn!("Could not get current epoch state {err}"), + Ok(epoch) => { + if self + .dkg_client + .group_member() + .await + .map(|resp| resp.weight.is_none()) + .unwrap_or(true) + { + debug!("Not a member of the group, DKG won't be run"); + return; + } + if let Err(err) = self.state.is_consistent(epoch.state).await { + error!("Epoch state is corrupted - {err}, the process should be terminated"); + return; + } + let ret = match epoch.state { + EpochState::PublicKeySubmission { resharing } => { + public_key_submission(&self.dkg_client, &mut self.state, resharing).await + } + EpochState::DealingExchange { resharing } => { + dealing_exchange( + &self.dkg_client, + &mut self.state, + self.rng.clone(), + resharing, + ) + .await + } + EpochState::VerificationKeySubmission { resharing } => { + let keypair_path = nym_pemstore::KeyPairPath::new( + self.secret_key_path.clone(), + self.verification_key_path.clone(), + ); + verification_key_submission( + &self.dkg_client, + &mut self.state, + &keypair_path, + resharing, + ) + .await + } + EpochState::VerificationKeyValidation { resharing } => { + verification_key_validation(&self.dkg_client, &mut self.state, resharing) + .await + } + EpochState::VerificationKeyFinalization { resharing } => { + verification_key_finalization(&self.dkg_client, &mut self.state, resharing) + .await + } + // Just wait, in case we need to redo dkg at some point + EpochState::InProgress => { + self.state.set_was_in_progress(); + Ok(()) + } + }; + if let Err(err) = ret { + warn!("Could not handle this iteration for the epoch state: {err}"); + } else if epoch.state != EpochState::InProgress { + let persistent_state = PersistentState::from(&self.state); + if let Err(err) = + persistent_state.save_to_file(self.state.persistent_state_path()) + { + warn!("Could not backup the state for this iteration: {err}"); + } + } + if let Ok(current_timestamp) = + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) + { + if current_timestamp.as_secs() >= epoch.finish_timestamp.seconds() { + // We try advancing the epoch state, on a best-effort basis + info!("DKG: Trying to advance the epoch"); + self.dkg_client.advance_epoch_state().await.ok(); + } + } + } + } + } + + pub(crate) async fn run(mut self, mut shutdown: TaskClient) { + let mut interval = interval(self.polling_rate); + while !shutdown.is_shutdown() { + tokio::select! { + _ = interval.tick() => self.handle_epoch_state().await, + _ = shutdown.recv() => { + trace!("DkgController: Received shutdown"); + } + } + } + } + + // TODO: can we make it non-async? it seems we'd have to modify `coconut_keypair.set(coconut_keypair_value)` in new + // could we do it? + pub(crate) async fn start( + config: &Config, + nyxd_client: nyxd::Client, + coconut_keypair: CoconutKeyPair, + rng: R, + shutdown: &TaskManager, + ) -> Result<()> + where + R: Sync + Send + 'static, + { + let shutdown_listener = shutdown.subscribe(); + let dkg_controller = DkgController::new(config, nyxd_client, coconut_keypair, rng).await?; + tokio::spawn(async move { dkg_controller.run(shutdown_listener).await }); + Ok(()) + } +} diff --git a/nym-api/src/coconut/dkg/dealing.rs b/nym-api/src/coconut/dkg/dealing.rs new file mode 100644 index 0000000000..7c8424acf6 --- /dev/null +++ b/nym-api/src/coconut/dkg/dealing.rs @@ -0,0 +1,331 @@ +// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::dkg::client::DkgClient; +use crate::coconut::dkg::state::{ConsistentState, State}; +use crate::coconut::error::CoconutError; +use nym_coconut_dkg_common::types::TOTAL_DEALINGS; +use nym_contracts_common::dealings::ContractSafeBytes; +use nym_dkg::bte::setup; +use nym_dkg::Dealing; +use rand::RngCore; +use std::collections::VecDeque; + +pub(crate) async fn dealing_exchange( + dkg_client: &DkgClient, + state: &mut State, + rng: impl RngCore + Clone, + resharing: bool, +) -> Result<(), CoconutError> { + if state.receiver_index().is_some() { + return Ok(()); + } + + let dealers = dkg_client.get_current_dealers().await?; + let threshold = dkg_client.get_current_epoch_threshold().await?; + let initial_dealers = dkg_client + .get_initial_dealers() + .await? + .map(|d| d.initial_dealers) + .unwrap_or_default(); + let own_address = dkg_client.get_address().await.as_ref().to_string(); + state.set_dealers(dealers); + state.set_threshold(threshold); + let receivers = state.current_dealers_by_idx(); + let dealer_index = state.node_index_value()?; + let receiver_index = receivers + .keys() + .position(|node_index| *node_index == dealer_index); + + let prior_resharing_secrets = if let Some(sk) = state.coconut_secret_key().await { + // Double check that we are in resharing mode + if resharing { + let (x, mut scalars) = sk.into_raw(); + if scalars.len() + 1 != TOTAL_DEALINGS { + return Err(CoconutError::CorruptedCoconutKeyPair); + } + // We can now erase the keypair from memory + state.set_coconut_keypair(None).await; + scalars.push(x); + scalars + } else { + log::warn!("Coconut key hasn't been reset in memory. The state might be corrupt"); + vec![] + } + } else { + vec![] + }; + let mut prior_resharing_secrets = VecDeque::from(prior_resharing_secrets); + if !resharing || initial_dealers.iter().any(|d| *d == own_address) { + let params = setup(); + for _ in 0..TOTAL_DEALINGS { + let (dealing, _) = Dealing::create( + rng.clone(), + ¶ms, + dealer_index, + state.threshold()?, + &receivers, + prior_resharing_secrets.pop_front(), + ); + dkg_client + .submit_dealing(ContractSafeBytes::from(&dealing), resharing) + .await?; + } + } + + info!("DKG: Finished submitting dealing"); + state.set_receiver_index(receiver_index); + + Ok(()) +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::coconut::dkg::complaints::ComplaintReason; + use crate::coconut::dkg::state::PersistentState; + use crate::coconut::tests::DummyClient; + use crate::coconut::KeyPair; + use cosmwasm_std::Addr; + use nym_coconut_dkg_common::dealer::DealerDetails; + use nym_coconut_dkg_common::types::InitialReplacementData; + use nym_dkg::bte::keys::KeyPair as DkgKeyPair; + use nym_dkg::bte::{Params, PublicKeyWithProof}; + use nymcoconut::{ttp_keygen, Parameters}; + use rand::rngs::OsRng; + use std::collections::HashMap; + use std::path::PathBuf; + use std::str::FromStr; + use std::sync::{Arc, RwLock}; + use url::Url; + use validator_client::nyxd::AccountId; + + const TEST_VALIDATORS_ADDRESS: [&str; 4] = [ + "n1aq9kakfgwqcufr23lsv644apavcntrsqsk4yus", + "n1s9l3xr4g0rglvk4yctktmck3h4eq0gp6z2e20v", + "n19kl4py32vsk297dm93ezem992cdyzdy4zuc2x6", + "n1jfrs6cmw9t7dv0x8cgny6geunzjh56n2s89fkv", + ]; + + fn insert_dealers( + params: &Params, + dealer_details_db: &Arc<RwLock<HashMap<String, DealerDetails>>>, + ) -> Vec<DkgKeyPair> { + let mut keypairs = vec![]; + for (idx, addr) in TEST_VALIDATORS_ADDRESS.iter().enumerate() { + let keypair = DkgKeyPair::new(params, OsRng); + let bte_public_key_with_proof = + bs58::encode(&keypair.public_key().to_bytes()).into_string(); + keypairs.push(keypair); + dealer_details_db.write().unwrap().insert( + addr.to_string(), + DealerDetails { + address: Addr::unchecked(*addr), + bte_public_key_with_proof, + announce_address: format!("localhost:80{}", idx), + assigned_index: (idx + 1) as u64, + }, + ); + } + keypairs + } + + #[tokio::test] + #[ignore] // expensive test + async fn exchange_dealing() { + let self_index = 2; + let dealer_details_db = Arc::new(RwLock::new(HashMap::new())); + let dealings_db = Arc::new(RwLock::new(HashMap::new())); + let threshold_db = Arc::new(RwLock::new(Some(2))); + let dkg_client = DkgClient::new( + DummyClient::new(AccountId::from_str(TEST_VALIDATORS_ADDRESS[0]).unwrap()) + .with_dealer_details(&dealer_details_db) + .with_dealings(&dealings_db) + .with_threshold(&threshold_db), + ); + let params = setup(); + let mut state = State::new( + PathBuf::default(), + PersistentState::default(), + Url::parse("localhost:8000").unwrap(), + DkgKeyPair::new(¶ms, OsRng), + KeyPair::new(), + ); + state.set_node_index(Some(self_index)); + let keypairs = insert_dealers(¶ms, &dealer_details_db); + + dealing_exchange(&dkg_client, &mut state, OsRng, false) + .await + .unwrap(); + + assert_eq!( + state.current_dealers_by_idx().values().collect::<Vec<_>>(), + keypairs + .iter() + .map(|k| k.public_key().public_key()) + .collect::<Vec<_>>() + ); + assert_eq!(state.threshold().unwrap(), 2); + assert_eq!(state.receiver_index().unwrap(), 1); + let dealings = dealings_db + .read() + .unwrap() + .get(TEST_VALIDATORS_ADDRESS[0]) + .unwrap() + .clone(); + assert_eq!(dealings.len(), TOTAL_DEALINGS); + + dealing_exchange(&dkg_client, &mut state, OsRng, false) + .await + .unwrap(); + let new_dealings = dealings_db + .read() + .unwrap() + .get(TEST_VALIDATORS_ADDRESS[0]) + .unwrap() + .clone(); + assert_eq!(dealings, new_dealings); + } + + #[tokio::test] + #[ignore] // expensive test + async fn invalid_bte_proof_dealing_posted() { + let self_index = 2; + let dealer_details_db = Arc::new(RwLock::new(HashMap::new())); + let dealings_db = Arc::new(RwLock::new(HashMap::new())); + let threshold_db = Arc::new(RwLock::new(Some(2))); + let dkg_client = DkgClient::new( + DummyClient::new(AccountId::from_str(TEST_VALIDATORS_ADDRESS[0]).unwrap()) + .with_dealer_details(&dealer_details_db) + .with_dealings(&dealings_db) + .with_threshold(&threshold_db), + ); + let params = setup(); + let mut state = State::new( + PathBuf::default(), + PersistentState::default(), + Url::parse("localhost:8000").unwrap(), + DkgKeyPair::new(¶ms, OsRng), + KeyPair::new(), + ); + state.set_node_index(Some(self_index)); + insert_dealers(¶ms, &dealer_details_db); + + dealer_details_db + .write() + .unwrap() + .entry(TEST_VALIDATORS_ADDRESS[1].to_string()) + .and_modify(|details| { + let mut bytes = bs58::decode(details.bte_public_key_with_proof.clone()) + .into_vec() + .unwrap(); + // Find another value for last byte that still deserializes to a public key with proof + let initial_byte = *bytes.last_mut().unwrap(); + loop { + let last_byte = bytes.last_mut().unwrap(); + let (ret, _) = last_byte.overflowing_add(1); + *last_byte = ret; + // stop when we find that value, or if we do a full round trip of u8 values + // and can't find one, in which case this test is invalid + if PublicKeyWithProof::try_from_bytes(&bytes).is_ok() || ret == initial_byte { + break; + } + } + details.bte_public_key_with_proof = bs58::encode(&bytes).into_string(); + }); + + dealing_exchange(&dkg_client, &mut state, OsRng, false) + .await + .unwrap(); + assert_eq!( + *state + .all_dealers() + .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[1])) + .unwrap() + .as_ref() + .unwrap_err(), + ComplaintReason::InvalidBTEPublicKey + ); + } + + #[tokio::test] + #[ignore] // expensive test + async fn resharing_exchange_dealing() { + let self_index = 2; + let dealer_details_db = Arc::new(RwLock::new(HashMap::new())); + let dealings_db = Arc::new(RwLock::new(HashMap::new())); + let threshold_db = Arc::new(RwLock::new(Some(3))); + let initial_dealers_db = Arc::new(RwLock::new(Some(InitialReplacementData { + initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])], + initial_height: Some(100), + }))); + let dkg_client = DkgClient::new( + DummyClient::new( + AccountId::from_str("n1vxkywf9g4cg0k2dehanzwzz64jw782qm0kuynf").unwrap(), + ) + .with_dealer_details(&dealer_details_db) + .with_dealings(&dealings_db) + .with_threshold(&threshold_db) + .with_initial_dealers_db(&initial_dealers_db), + ); + let params = setup(); + let mut keys = ttp_keygen(&Parameters::new(4).unwrap(), 3, 4).unwrap(); + let coconut_keypair = KeyPair::new(); + coconut_keypair.set(Some(keys.pop().unwrap())).await; + + let mut state = State::new( + PathBuf::default(), + PersistentState::default(), + Url::parse("localhost:8000").unwrap(), + DkgKeyPair::new(¶ms, OsRng), + coconut_keypair.clone(), + ); + state.set_node_index(Some(self_index)); + let keypairs = insert_dealers(¶ms, &dealer_details_db); + + dealing_exchange(&dkg_client, &mut state, OsRng, true) + .await + .unwrap(); + + assert_eq!( + state.current_dealers_by_idx().values().collect::<Vec<_>>(), + keypairs + .iter() + .map(|k| k.public_key().public_key()) + .collect::<Vec<_>>() + ); + assert_eq!(state.threshold().unwrap(), 3); + assert_eq!(state.receiver_index().unwrap(), 1); + let addr = dkg_client.get_address().await; + assert!(dealings_db.read().unwrap().get(addr.as_ref()).is_none()); + + let mut state = State::new( + PathBuf::default(), + PersistentState::default(), + Url::parse("localhost:8000").unwrap(), + DkgKeyPair::new(¶ms, OsRng), + coconut_keypair, + ); + state.set_node_index(Some(self_index)); + // Use a client that is in the initial dealers set + let dkg_client = DkgClient::new( + DummyClient::new(AccountId::from_str(TEST_VALIDATORS_ADDRESS[0]).unwrap()) + .with_dealer_details(&dealer_details_db) + .with_dealings(&dealings_db) + .with_threshold(&threshold_db) + .with_initial_dealers_db(&initial_dealers_db), + ); + + dealing_exchange(&dkg_client, &mut state, OsRng, true) + .await + .unwrap(); + + let dealings = dealings_db + .read() + .unwrap() + .get(TEST_VALIDATORS_ADDRESS[0]) + .unwrap() + .clone(); + assert_eq!(dealings.len(), TOTAL_DEALINGS); + } +} diff --git a/validator-api/src/coconut/dkg/mod.rs b/nym-api/src/coconut/dkg/mod.rs similarity index 100% rename from validator-api/src/coconut/dkg/mod.rs rename to nym-api/src/coconut/dkg/mod.rs diff --git a/validator-api/src/coconut/dkg/public_key.rs b/nym-api/src/coconut/dkg/public_key.rs similarity index 70% rename from validator-api/src/coconut/dkg/public_key.rs rename to nym-api/src/coconut/dkg/public_key.rs index 0ce3bd45b3..cf779cd8a1 100644 --- a/validator-api/src/coconut/dkg/public_key.rs +++ b/nym-api/src/coconut/dkg/public_key.rs @@ -4,25 +4,34 @@ use crate::coconut::dkg::client::DkgClient; use crate::coconut::dkg::state::State; use crate::coconut::error::CoconutError; +use nym_coconut_dkg_common::dealer::DealerType; pub(crate) async fn public_key_submission( dkg_client: &DkgClient, state: &mut State, + resharing: bool, ) -> Result<(), CoconutError> { + if state.was_in_progress() { + state.reset_persistent(resharing).await; + } if state.node_index().is_some() { return Ok(()); } let bte_key = bs58::encode(&state.dkg_keypair().public_key().to_bytes()).into_string(); - let index = if let Some(details) = dkg_client - .get_self_registered_dealer_details() - .await? - .details - { + let dealer_details = dkg_client.get_self_registered_dealer_details().await?; + let index = if let Some(details) = dealer_details.details { + if dealer_details.dealer_type == DealerType::Past { + // If it was a dealer in a previous epoch, re-register it for this epoch + dkg_client + .register_dealer(bte_key, state.announce_address().to_string(), resharing) + .await?; + } details.assigned_index } else { + // First time registration dkg_client - .register_dealer(bte_key, state.announce_address().to_string()) + .register_dealer(bte_key, state.announce_address().to_string(), resharing) .await? }; state.set_node_index(Some(index)); @@ -37,12 +46,12 @@ pub(crate) mod tests { use crate::coconut::dkg::state::PersistentState; use crate::coconut::tests::DummyClient; use crate::coconut::KeyPair; - use dkg::bte::keys::KeyPair as DkgKeyPair; + use nym_dkg::bte::keys::KeyPair as DkgKeyPair; use rand::rngs::OsRng; use std::path::PathBuf; use std::str::FromStr; use url::Url; - use validator_client::nymd::AccountId; + use validator_client::nyxd::AccountId; const TEST_VALIDATOR_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; @@ -56,7 +65,7 @@ pub(crate) mod tests { PathBuf::default(), PersistentState::default(), Url::parse("localhost:8000").unwrap(), - DkgKeyPair::new(&dkg::bte::setup(), OsRng), + DkgKeyPair::new(&nym_dkg::bte::setup(), OsRng), KeyPair::new(), ); @@ -66,7 +75,7 @@ pub(crate) mod tests { .unwrap() .details .is_none()); - public_key_submission(&dkg_client, &mut state) + public_key_submission(&dkg_client, &mut state, false) .await .unwrap(); let client_idx = dkg_client @@ -80,7 +89,7 @@ pub(crate) mod tests { // keeps the same index from chain, not calling register_dealer again state.set_node_index(None); - public_key_submission(&dkg_client, &mut state) + public_key_submission(&dkg_client, &mut state, false) .await .unwrap(); assert_eq!(state.node_index().unwrap(), client_idx); diff --git a/validator-api/src/coconut/dkg/state.rs b/nym-api/src/coconut/dkg/state.rs similarity index 85% rename from validator-api/src/coconut/dkg/state.rs rename to nym-api/src/coconut/dkg/state.rs index 9ba1dfaffd..6d37161572 100644 --- a/validator-api/src/coconut/dkg/state.rs +++ b/nym-api/src/coconut/dkg/state.rs @@ -4,11 +4,12 @@ use crate::coconut::dkg::complaints::ComplaintReason; use crate::coconut::error::CoconutError; use crate::coconut::keypair::KeyPair as CoconutKeyPair; -use coconut_dkg_common::dealer::DealerDetails; -use coconut_dkg_common::types::EpochState; use cosmwasm_std::Addr; -use dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof}; -use dkg::{NodeIndex, RecoveredVerificationKeys, Threshold}; +use nym_coconut_dkg_common::dealer::DealerDetails; +use nym_coconut_dkg_common::types::EpochState; +use nym_dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof}; +use nym_dkg::{NodeIndex, RecoveredVerificationKeys, Threshold}; +use nymcoconut::SecretKey; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::BTreeMap; @@ -71,18 +72,18 @@ pub(crate) trait ConsistentState { fn proposal_id_value(&self) -> Result<u64, CoconutError>; async fn is_consistent(&self, epoch_state: EpochState) -> Result<(), CoconutError> { match epoch_state { - EpochState::PublicKeySubmission => {} - EpochState::DealingExchange => { + EpochState::PublicKeySubmission { .. } => {} + EpochState::DealingExchange { .. } => { self.node_index_value()?; } - EpochState::VerificationKeySubmission => { + EpochState::VerificationKeySubmission { .. } => { self.receiver_index_value()?; self.threshold()?; } - EpochState::VerificationKeyValidation => { + EpochState::VerificationKeyValidation { .. } => { self.coconut_keypair_is_some().await?; } - EpochState::VerificationKeyFinalization => { + EpochState::VerificationKeyFinalization { .. } => { self.proposal_id_value()?; } EpochState::InProgress => {} @@ -170,6 +171,7 @@ pub(crate) struct PersistentState { proposal_id: Option<u64>, voted_vks: bool, executed_proposal: bool, + was_in_progress: bool, } impl From<&State> for PersistentState { @@ -183,6 +185,7 @@ impl From<&State> for PersistentState { proposal_id: s.proposal_id, voted_vks: s.voted_vks, executed_proposal: s.executed_proposal, + was_in_progress: s.was_in_progress, } } } @@ -211,6 +214,7 @@ pub(crate) struct State { proposal_id: Option<u64>, voted_vks: bool, executed_proposal: bool, + was_in_progress: bool, } impl State { @@ -234,9 +238,25 @@ impl State { proposal_id: persistent_state.proposal_id, voted_vks: persistent_state.voted_vks, executed_proposal: persistent_state.executed_proposal, + was_in_progress: persistent_state.was_in_progress, } } + pub async fn reset_persistent(&mut self, resharing: bool) { + if !resharing { + self.coconut_keypair.set(None).await; + } + self.node_index = Default::default(); + self.dealers = Default::default(); + self.receiver_index = Default::default(); + self.threshold = Default::default(); + self.recovered_vks = Default::default(); + self.proposal_id = Default::default(); + self.voted_vks = Default::default(); + self.executed_proposal = Default::default(); + self.was_in_progress = Default::default(); + } + pub fn persistent_state_path(&self) -> PathBuf { self.persistent_state_path.clone() } @@ -253,6 +273,14 @@ impl State { self.coconut_keypair.get().await.is_some() } + pub async fn coconut_secret_key(&self) -> Option<SecretKey> { + self.coconut_keypair + .get() + .await + .as_ref() + .map(|kp| kp.secret_key()) + } + pub fn node_index(&self) -> Option<NodeIndex> { self.node_index } @@ -299,11 +327,18 @@ impl State { self.executed_proposal } + pub fn was_in_progress(&self) -> bool { + self.was_in_progress + } + pub fn set_recovered_vks(&mut self, recovered_vks: Vec<RecoveredVerificationKeys>) { self.recovered_vks = recovered_vks; } - pub async fn set_coconut_keypair(&mut self, coconut_keypair: coconut_interface::KeyPair) { + pub async fn set_coconut_keypair( + &mut self, + coconut_keypair: Option<nym_coconut_interface::KeyPair>, + ) { self.coconut_keypair.set(coconut_keypair).await } @@ -349,6 +384,10 @@ impl State { self.executed_proposal = true; } + pub fn set_was_in_progress(&mut self) { + self.was_in_progress = true; + } + #[cfg(test)] pub fn all_dealers(&self) -> &BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>> { &self.dealers diff --git a/validator-api/src/coconut/dkg/verification_key.rs b/nym-api/src/coconut/dkg/verification_key.rs similarity index 65% rename from validator-api/src/coconut/dkg/verification_key.rs rename to nym-api/src/coconut/dkg/verification_key.rs index 1ef88e0d54..402db402ee 100644 --- a/validator-api/src/coconut/dkg/verification_key.rs +++ b/nym-api/src/coconut/dkg/verification_key.rs @@ -5,30 +5,43 @@ 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 coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID; -use coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS}; -use coconut_dkg_common::verification_key::owner_from_cosmos_msgs; -use coconut_interface::KeyPair as CoconutKeyPair; +use crate::coconut::helpers::accepted_vote_err; use cosmwasm_std::Addr; -use credentials::coconut::bandwidth::{PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; use cw3::{ProposalResponse, Status}; -use dkg::bte::{decrypt_share, setup}; -use dkg::{combine_shares, try_recover_verification_keys, Dealing, Threshold}; +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}; +use nym_pemstore::KeyPairPath; use nymcoconut::tests::helpers::transpose_matrix; use nymcoconut::{check_vk_pairing, Base58, KeyPair, Parameters, SecretKey, VerificationKey}; -use pemstore::KeyPairPath; use std::collections::BTreeMap; -use validator_client::nymd::cosmwasm_client::logs::find_attribute; +use validator_client::nyxd::cosmwasm_client::logs::find_attribute; // Filter the dealers based on what dealing they posted (or not) in the contract async fn deterministic_filter_dealers( dkg_client: &DkgClient, state: &mut State, threshold: Threshold, + resharing: bool, ) -> Result<Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>, CoconutError> { let mut dealings_maps = vec![]; let initial_dealers_by_addr = state.current_dealers_by_addr(); let initial_receivers = state.current_dealers_by_idx(); + let initial_resharing_dealers = if resharing { + dkg_client + .get_initial_dealers() + .await? + .map(|d| d.initial_dealers) + .unwrap_or_default() + } else { + vec![] + }; + let params = setup(); for idx in 0..TOTAL_DEALINGS { @@ -65,11 +78,15 @@ async fn deterministic_filter_dealers( })); dealings_maps.push(dealings_map); } + for (addr, _) in initial_dealers_by_addr.iter() { - for dealings_map in dealings_maps.iter() { - if !dealings_map.iter().any(|(_, (address, _))| address == addr) { - state.mark_bad_dealer(addr, ComplaintReason::MissingDealing); - break; + // in resharing mode, we don't commit dealings from dealers outside the initial set + if !resharing || initial_resharing_dealers.contains(addr) { + for dealings_map in dealings_maps.iter() { + if !dealings_map.iter().any(|(_, (address, _))| address == addr) { + state.mark_bad_dealer(addr, ComplaintReason::MissingDealing); + break; + } } } } @@ -89,16 +106,16 @@ fn derive_partial_keypair( let mut scalars = vec![]; let mut recovered_vks = vec![]; for dealings_map in dealings_maps.into_iter() { - let filtered_dealings: Vec<_> = dealings_map + let (filtered_dealers, filtered_dealings): (Vec<_>, Vec<_>) = dealings_map .into_iter() - .filter_map(|(_, (addr, dealing))| { + .filter_map(|(idx, (addr, dealing))| { if filtered_dealers_by_addr.keys().any(|a| addr == *a) { - Some(dealing) + Some((idx, dealing)) } else { None } }) - .collect(); + .unzip(); let recovered = try_recover_verification_keys( &filtered_dealings, threshold, @@ -110,19 +127,18 @@ fn derive_partial_keypair( .iter() .map(|dealing| decrypt_share(dk, node_index_value, &dealing.ciphertexts, None)) .collect::<Result<_, _>>()?; - let scalar = combine_shares( - shares, - &filtered_receivers_by_idx - .keys() - .copied() - .collect::<Vec<_>>(), - )?; + let scalar = combine_shares(shares, &filtered_dealers)?; scalars.push(scalar); } state.set_recovered_vks(recovered_vks); let params = Parameters::new(PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES)?; - let x = scalars.pop().unwrap(); + let x = scalars.pop().ok_or(CoconutError::DkgError( + DkgError::NotEnoughDealingsAvailable { + available: 0, + required: 1, + }, + ))?; let sk = SecretKey::create_from_raw(x, scalars); let vk = sk.verification_key(¶ms); @@ -133,17 +149,21 @@ pub(crate) async fn verification_key_submission( dkg_client: &DkgClient, state: &mut State, keypair_path: &KeyPairPath, + resharing: bool, ) -> Result<(), CoconutError> { if state.coconut_keypair_is_some().await { return Ok(()); } let threshold = state.threshold()?; - let dealings_maps = deterministic_filter_dealers(dkg_client, state, threshold).await?; + let dealings_maps = + deterministic_filter_dealers(dkg_client, state, threshold, resharing).await?; let coconut_keypair = derive_partial_keypair(state, threshold, dealings_maps)?; let vk_share = coconut_keypair.verification_key().to_bs58(); - pemstore::store_keypair(&coconut_keypair, keypair_path)?; - let res = dkg_client.submit_verification_key_share(vk_share).await?; + nym_pemstore::store_keypair(&coconut_keypair, keypair_path)?; + let res = dkg_client + .submit_verification_key_share(vk_share, resharing) + .await?; let proposal_id = find_attribute(&res.logs, "wasm", DKG_PROPOSAL_ID) .ok_or(CoconutError::ProposalIdError { reason: String::from("proposal id not found"), @@ -154,7 +174,7 @@ pub(crate) async fn verification_key_submission( reason: String::from("proposal id could not be parsed to u64"), })?; state.set_proposal_id(proposal_id); - state.set_coconut_keypair(coconut_keypair).await; + state.set_coconut_keypair(Some(coconut_keypair)).await; info!("DKG: Submitted own verification key"); Ok(()) @@ -172,12 +192,14 @@ fn validate_proposal(proposal: &ProposalResponse) -> Option<(Addr, u64)> { pub(crate) async fn verification_key_validation( dkg_client: &DkgClient, state: &mut State, + _resharing: bool, ) -> Result<(), CoconutError> { if state.voted_vks() { return Ok(()); } - let vk_shares = dkg_client.get_verification_key_shares().await?; + let epoch_id = dkg_client.get_current_epoch().await?.epoch_id; + let vk_shares = dkg_client.get_verification_key_shares(epoch_id).await?; let proposal_ids = BTreeMap::from_iter( dkg_client .list_proposals() @@ -202,21 +224,23 @@ pub(crate) async fn verification_key_validation( .iter() .position(|node_index| contract_share.node_index == *node_index) { - if !check_vk_pairing(¶ms, &recovered_partials[idx], &vk) { + let ret = if !check_vk_pairing(¶ms, &recovered_partials[idx], &vk) { dkg_client .vote_verification_key_share(proposal_id, false) - .await?; + .await } else { dkg_client .vote_verification_key_share(proposal_id, true) - .await?; - } + .await + }; + accepted_vote_err(ret)?; } } Err(_) => { - dkg_client + let ret = dkg_client .vote_verification_key_share(proposal_id, false) - .await? + .await; + accepted_vote_err(ret)?; } } } @@ -229,6 +253,7 @@ pub(crate) async fn verification_key_validation( pub(crate) async fn verification_key_finalization( dkg_client: &DkgClient, state: &mut State, + _resharing: bool, ) -> Result<(), CoconutError> { if state.executed_proposal() { return Ok(()); @@ -252,10 +277,12 @@ pub(crate) mod tests { use crate::coconut::dkg::state::PersistentState; use crate::coconut::tests::DummyClient; use crate::coconut::KeyPair; - use coconut_dkg_common::dealer::DealerDetails; - use coconut_dkg_common::verification_key::ContractVKShare; - use contracts_common::dealings::ContractSafeBytes; - use dkg::bte::keys::KeyPair as DkgKeyPair; + use nym_coconut_dkg_common::dealer::DealerDetails; + use nym_coconut_dkg_common::types::InitialReplacementData; + use nym_coconut_dkg_common::verification_key::ContractVKShare; + use nym_contracts_common::dealings::ContractSafeBytes; + use nym_dkg::bte::keys::KeyPair as DkgKeyPair; + use nymcoconut::aggregate_verification_keys; use rand::rngs::OsRng; use rand::Rng; use std::collections::HashMap; @@ -264,7 +291,7 @@ pub(crate) mod tests { use std::str::FromStr; use std::sync::{Arc, RwLock}; use url::Url; - use validator_client::nymd::AccountId; + use validator_client::nyxd::AccountId; struct MockContractDb { dealer_details_db: Arc<RwLock<HashMap<String, DealerDetails>>>, @@ -272,6 +299,7 @@ pub(crate) mod tests { proposal_db: Arc<RwLock<HashMap<u64, ProposalResponse>>>, verification_share_db: Arc<RwLock<HashMap<String, ContractVKShare>>>, threshold_db: Arc<RwLock<Option<Threshold>>>, + initial_dealers_db: Arc<RwLock<Option<InitialReplacementData>>>, } impl MockContractDb { @@ -282,6 +310,7 @@ pub(crate) mod tests { proposal_db: Arc::new(Default::default()), verification_share_db: Arc::new(Default::default()), threshold_db: Arc::new(RwLock::new(Some(2))), + initial_dealers_db: Arc::new(RwLock::new(Default::default())), } } } @@ -303,7 +332,8 @@ pub(crate) mod tests { .with_dealings(&db.dealings_db) .with_proposal_db(&db.proposal_db) .with_verification_share(&db.verification_share_db) - .with_threshold(&db.threshold_db), + .with_threshold(&db.threshold_db) + .with_initial_dealers_db(&db.initial_dealers_db), ); let keypair = DkgKeyPair::new(¶ms, OsRng); let state = State::new( @@ -316,10 +346,21 @@ pub(crate) mod tests { clients_and_states.push((dkg_client, state)); } for (dkg_client, state) in clients_and_states.iter_mut() { - public_key_submission(dkg_client, state).await.unwrap(); + public_key_submission(dkg_client, state, false) + .await + .unwrap(); } + clients_and_states + } + + async fn prepare_clients_and_states_with_dealing( + db: &MockContractDb, + ) -> Vec<(DkgClient, State)> { + let mut clients_and_states = prepare_clients_and_states(db).await; for (dkg_client, state) in clients_and_states.iter_mut() { - dealing_exchange(dkg_client, state, OsRng).await.unwrap(); + dealing_exchange(dkg_client, state, OsRng, false) + .await + .unwrap(); } clients_and_states } @@ -327,13 +368,13 @@ pub(crate) mod tests { async fn prepare_clients_and_states_with_submission( db: &MockContractDb, ) -> Vec<(DkgClient, State)> { - let mut clients_and_states = prepare_clients_and_states(db).await; + let mut clients_and_states = prepare_clients_and_states_with_dealing(db).await; for (dkg_client, state) in clients_and_states.iter_mut() { let random_file: usize = OsRng.gen(); let private_key_path = temp_dir().join(format!("private{}.pem", random_file)); let public_key_path = temp_dir().join(format!("public{}.pem", random_file)); let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone()); - verification_key_submission(dkg_client, state, &keypair_path) + verification_key_submission(dkg_client, state, &keypair_path, false) .await .unwrap(); std::fs::remove_file(private_key_path).unwrap(); @@ -347,7 +388,7 @@ pub(crate) mod tests { ) -> Vec<(DkgClient, State)> { let mut clients_and_states = prepare_clients_and_states_with_submission(db).await; for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_validation(dkg_client, state) + verification_key_validation(dkg_client, state, false) .await .unwrap(); } @@ -359,7 +400,7 @@ pub(crate) mod tests { ) -> Vec<(DkgClient, State)> { let mut clients_and_states = prepare_clients_and_states_with_validation(db).await; for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_finalization(dkg_client, state) + verification_key_finalization(dkg_client, state, false) .await .unwrap(); } @@ -370,9 +411,9 @@ pub(crate) mod tests { #[ignore] // expensive test async fn check_dealers_filter_all_good() { let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states(&db).await; + let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; for (dkg_client, state) in clients_and_states.iter_mut() { - let filtered = deterministic_filter_dealers(dkg_client, state, 2) + let filtered = deterministic_filter_dealers(dkg_client, state, 2, false) .await .unwrap(); assert_eq!(filtered.len(), TOTAL_DEALINGS); @@ -386,7 +427,7 @@ pub(crate) mod tests { #[ignore] // expensive test async fn check_dealers_filter_one_bad_dealing() { let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states(&db).await; + let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; // corrupt just one dealing db.dealings_db @@ -400,7 +441,7 @@ pub(crate) mod tests { }); for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - let filtered = deterministic_filter_dealers(dkg_client, state, 2) + let filtered = deterministic_filter_dealers(dkg_client, state, 2, false) .await .unwrap(); assert_eq!(filtered.len(), TOTAL_DEALINGS); @@ -416,10 +457,74 @@ pub(crate) mod tests { #[tokio::test] #[ignore] // expensive test - async fn check_dealers_filter_all_bad_dealings() { + async fn check_dealers_resharing_filter_one_missing_dealing() { let db = MockContractDb::new(); let mut clients_and_states = prepare_clients_and_states(&db).await; + // add all but the first dealing + for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { + dealing_exchange(dkg_client, state, OsRng, true) + .await + .unwrap(); + } + + for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { + *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { + initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])], + initial_height: None, + }); + let filtered = deterministic_filter_dealers(dkg_client, state, 2, true) + .await + .unwrap(); + assert_eq!(filtered.len(), TOTAL_DEALINGS); + let corrupted_status = state + .all_dealers() + .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) + .unwrap() + .as_ref() + .unwrap_err(); + + assert_eq!(*corrupted_status, ComplaintReason::MissingDealing); + } + } + + #[tokio::test] + #[ignore] // expensive test + async fn check_dealers_resharing_filter_one_noninitial_missing_dealing() { + let db = MockContractDb::new(); + let mut clients_and_states = prepare_clients_and_states(&db).await; + + // add all but the first dealing + for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { + dealing_exchange(dkg_client, state, OsRng, true) + .await + .unwrap(); + } + + for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { + *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { + initial_dealers: vec![], + initial_height: None, + }); + let filtered = deterministic_filter_dealers(dkg_client, state, 2, true) + .await + .unwrap(); + assert_eq!(filtered.len(), TOTAL_DEALINGS); + assert!(state + .all_dealers() + .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) + .unwrap() + .as_ref() + .is_ok(),); + } + } + + #[tokio::test] + #[ignore] // expensive test + async fn check_dealers_filter_all_bad_dealings() { + let db = MockContractDb::new(); + let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; + // corrupt all dealings of one address db.dealings_db .write() @@ -432,7 +537,7 @@ pub(crate) mod tests { }); for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - let filtered = deterministic_filter_dealers(dkg_client, state, 2) + let filtered = deterministic_filter_dealers(dkg_client, state, 2, false) .await .unwrap(); assert_eq!(filtered.len(), TOTAL_DEALINGS); @@ -453,7 +558,7 @@ pub(crate) mod tests { #[ignore] // expensive test async fn check_dealers_filter_malformed_dealing() { let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states(&db).await; + let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; // corrupt just one dealing db.dealings_db @@ -467,12 +572,12 @@ pub(crate) mod tests { }); for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - deterministic_filter_dealers(dkg_client, state, 2) + deterministic_filter_dealers(dkg_client, state, 2, false) .await .unwrap(); // second filter will leave behind the bad dealer and surface why it was left out // in the first place - let filtered = deterministic_filter_dealers(dkg_client, state, 2) + let filtered = deterministic_filter_dealers(dkg_client, state, 2, false) .await .unwrap(); assert_eq!(filtered.len(), TOTAL_DEALINGS); @@ -490,7 +595,7 @@ pub(crate) mod tests { #[ignore] // expensive test async fn check_dealers_filter_dealing_verification_error() { let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states(&db).await; + let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; // corrupt just one dealing db.dealings_db @@ -499,18 +604,22 @@ pub(crate) mod tests { .entry(TEST_VALIDATORS_ADDRESS[0].to_string()) .and_modify(|dealings| { let mut last = dealings.pop().unwrap(); - last.0.pop(); - last.0.push(42); + let value = last.0.pop().unwrap(); + if value == 42 { + last.0.push(43); + } else { + last.0.push(42); + } dealings.push(last); }); for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - deterministic_filter_dealers(dkg_client, state, 2) + deterministic_filter_dealers(dkg_client, state, 2, false) .await .unwrap(); // second filter will leave behind the bad dealer and surface why it was left out // in the first place - let filtered = deterministic_filter_dealers(dkg_client, state, 2) + let filtered = deterministic_filter_dealers(dkg_client, state, 2, false) .await .unwrap(); assert_eq!(filtered.len(), TOTAL_DEALINGS); @@ -528,9 +637,9 @@ pub(crate) mod tests { #[ignore] // expensive test async fn partial_keypair_derivation() { let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states(&db).await; + let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; for (dkg_client, state) in clients_and_states.iter_mut() { - let filtered = deterministic_filter_dealers(dkg_client, state, 2) + let filtered = deterministic_filter_dealers(dkg_client, state, 2, false) .await .unwrap(); assert!(derive_partial_keypair(state, 2, filtered).is_ok()); @@ -541,7 +650,7 @@ pub(crate) mod tests { #[ignore] // expensive test async fn partial_keypair_derivation_with_threshold() { let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states(&db).await; + let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; // corrupt just one dealing db.dealings_db @@ -555,7 +664,7 @@ pub(crate) mod tests { }); for (dkg_client, state) in clients_and_states.iter_mut().skip(1) { - let filtered = deterministic_filter_dealers(dkg_client, state, 2) + let filtered = deterministic_filter_dealers(dkg_client, state, 2, false) .await .unwrap(); assert!(derive_partial_keypair(state, 2, filtered).is_ok()); @@ -608,7 +717,7 @@ pub(crate) mod tests { .and_modify(|share| share.share.push('x')); for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_validation(dkg_client, state) + verification_key_validation(dkg_client, state, false) .await .unwrap(); } @@ -650,7 +759,7 @@ pub(crate) mod tests { .and_modify(|share| share.share = second_share); for (dkg_client, state) in clients_and_states.iter_mut() { - verification_key_validation(dkg_client, state) + verification_key_validation(dkg_client, state, false) .await .unwrap(); } @@ -688,4 +797,102 @@ pub(crate) mod tests { assert_eq!(proposal.status, Status::Executed); } } + + #[tokio::test] + #[ignore] // expensive test + async fn reshare_preserves_keys() { + let db = MockContractDb::new(); + let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await; + 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() + .await + .unwrap() + .verification_key(¶ms); + let index = state.node_index().unwrap(); + vks.push(vk); + indices.push(index); + } + let initial_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap(); + + let new_dkg_client = DkgClient::new( + DummyClient::new( + AccountId::from_str("n1sqkxzh7nl6kgndr4ew9795t2nkwmd8tpql67q7").unwrap(), + ) + .with_dealer_details(&db.dealer_details_db) + .with_dealings(&db.dealings_db) + .with_proposal_db(&db.proposal_db) + .with_verification_share(&db.verification_share_db) + .with_threshold(&db.threshold_db) + .with_initial_dealers_db(&db.initial_dealers_db), + ); + let keypair = DkgKeyPair::new(&setup(), OsRng); + let state = State::new( + PathBuf::default(), + PersistentState::default(), + Url::parse("localhost:8000").unwrap(), + keypair, + KeyPair::new(), + ); + + let removed_dealer = clients_and_states.first().unwrap().0.get_address().await; + db.dealer_details_db + .write() + .unwrap() + .remove(removed_dealer.as_ref()); + *db.dealings_db.write().unwrap() = Default::default(); + *db.verification_share_db.write().unwrap() = Default::default(); + *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { + initial_dealers: vec![ + Addr::unchecked(clients_and_states[1].0.get_address().await.as_ref()), + Addr::unchecked(clients_and_states[2].0.get_address().await.as_ref()), + ], + initial_height: None, + }); + *clients_and_states.first_mut().unwrap() = (new_dkg_client, state); + clients_and_states[1].1.set_was_in_progress(); + clients_and_states[2].1.set_was_in_progress(); + + for (dkg_client, state) in clients_and_states.iter_mut() { + public_key_submission(dkg_client, state, true) + .await + .unwrap(); + } + + for (dkg_client, state) in clients_and_states.iter_mut() { + dealing_exchange(dkg_client, state, OsRng, true) + .await + .unwrap(); + } + + for (dkg_client, state) in clients_and_states.iter_mut() { + let random_file: usize = OsRng.gen(); + let private_key_path = temp_dir().join(format!("private{}.pem", random_file)); + let public_key_path = temp_dir().join(format!("public{}.pem", random_file)); + let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone()); + verification_key_submission(dkg_client, state, &keypair_path, true) + .await + .unwrap(); + std::fs::remove_file(private_key_path).unwrap(); + std::fs::remove_file(public_key_path).unwrap(); + } + let mut vks = vec![]; + let mut indices = vec![]; + for (_, state) in clients_and_states.iter() { + let vk = state + .coconut_secret_key() + .await + .unwrap() + .verification_key(¶ms); + let index = state.node_index().unwrap(); + vks.push(vk); + indices.push(index); + } + let reshared_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap(); + assert_eq!(initial_master_vk, reshared_master_vk); + } } diff --git a/validator-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs similarity index 82% rename from validator-api/src/coconut/error.rs rename to nym-api/src/coconut/error.rs index 234d1cb96c..387f4219f0 100644 --- a/validator-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -7,36 +7,36 @@ use rocket::{response, Request, Response}; use std::io::Cursor; use thiserror::Error; -use crypto::asymmetric::{ +use nym_crypto::asymmetric::{ encryption::KeyRecoveryError, identity::{Ed25519RecoveryError, SignatureError}, }; -use dkg::error::DkgError; -use validator_client::nymd::error::NymdError; +use nym_dkg::error::DkgError; +use validator_client::nyxd::error::NyxdError; -use crate::node_status_api::models::ValidatorApiStorageError; +use crate::node_status_api::models::NymApiStorageError; pub type Result<T> = std::result::Result<T, CoconutError>; #[derive(Debug, Error)] pub enum CoconutError { - #[error("{0}")] + #[error(transparent)] IOError(#[from] std::io::Error), - #[error("{0}")] + #[error(transparent)] SerdeJsonError(#[from] serde_json::Error), - #[error("Could not parse Ed25519 data")] + #[error("Could not parse Ed25519 data - {0}")] Ed25519ParseError(#[from] Ed25519RecoveryError), - #[error("Could not parse X25519 data")] + #[error("Could not parse X25519 data - {0}")] X25519ParseError(#[from] KeyRecoveryError), #[error("Could not parse tx hash in request body")] TxHashParseError, - #[error("Nymd error - {0}")] - NymdError(#[from] NymdError), + #[error("Nyxd error - {0}")] + NyxdError(#[from] NyxdError), #[error("Validator client error - {0}")] ValidatorClientError(#[from] validator_client::ValidatorClientError), @@ -70,17 +70,14 @@ pub enum CoconutError { )] DifferentPublicAttributes(String, String), - #[error("No signature found")] - NoSignature, - #[error("Error in coconut interface - {0}")] - CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), + CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), #[error("Storage error - {0}")] - StorageError(#[from] ValidatorApiStorageError), + StorageError(#[from] NymApiStorageError), #[error("Credentials error - {0}")] - CredentialsError(#[from] credentials::error::Error), + CredentialsError(#[from] nym_credentials::error::Error), #[error("Incorrect credential proposal description: {reason}")] IncorrectProposal { reason: String }, @@ -100,6 +97,9 @@ pub enum CoconutError { #[error("DKG has not finished yet in order to derive the coconut key")] KeyPairNotDerivedYet, + #[error("The coconut keypair is corrupted")] + CorruptedCoconutKeyPair, + #[error("There was a problem with the proposal id: {reason}")] ProposalIdError { reason: String }, } diff --git a/nym-api/src/coconut/helpers.rs b/nym-api/src/coconut/helpers.rs new file mode 100644 index 0000000000..5d2cda6a6d --- /dev/null +++ b/nym-api/src/coconut/helpers.rs @@ -0,0 +1,19 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::error::CoconutError; +use validator_client::nyxd::error::NyxdError::AbciError; + +// If the result is already established, the vote might be redundant and +// thus the transaction might fail +pub(crate) fn accepted_vote_err(ret: Result<(), CoconutError>) -> Result<(), CoconutError> { + if let Err(CoconutError::NyxdError(AbciError { ref log, .. })) = ret { + let accepted_err = + nym_multisig_contract_common::error::ContractError::NotOpen {}.to_string(); + // If redundant voting is not the case, error out on all other error variants + if !log.value().contains(&accepted_err) { + ret?; + } + } + Ok(()) +} diff --git a/validator-api/src/coconut/keypair.rs b/nym-api/src/coconut/keypair.rs similarity index 75% rename from validator-api/src/coconut/keypair.rs rename to nym-api/src/coconut/keypair.rs index 680f1c4d13..330a1a26de 100644 --- a/validator-api/src/coconut/keypair.rs +++ b/nym-api/src/coconut/keypair.rs @@ -6,7 +6,7 @@ use tokio::sync::{RwLock, RwLockReadGuard}; #[derive(Clone, Debug)] pub struct KeyPair { - inner: Arc<RwLock<Option<coconut_interface::KeyPair>>>, + inner: Arc<RwLock<Option<nym_coconut_interface::KeyPair>>>, } impl KeyPair { @@ -20,8 +20,8 @@ impl KeyPair { self.inner.read().await } - pub async fn set(&self, keypair: coconut_interface::KeyPair) { + pub async fn set(&self, keypair: Option<nym_coconut_interface::KeyPair>) { let mut w_lock = self.inner.write().await; - *w_lock = Some(keypair); + *w_lock = keypair; } } diff --git a/validator-api/src/coconut/mod.rs b/nym-api/src/coconut/mod.rs similarity index 81% rename from validator-api/src/coconut/mod.rs rename to nym-api/src/coconut/mod.rs index 25bcb4afa0..d28b734d8d 100644 --- a/validator-api/src/coconut/mod.rs +++ b/nym-api/src/coconut/mod.rs @@ -1,57 +1,57 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -pub(crate) mod client; -pub(crate) mod comm; -mod deposit; -pub(crate) mod dkg; -pub(crate) mod error; -pub(crate) mod keypair; -#[cfg(test)] -pub(crate) mod tests; - +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::ValidatorApiStorage; - -use coconut_bandwidth_contract_common::spend_credential::{ - funds_from_cosmos_msgs, SpendCredentialStatus, -}; -use coconut_interface::KeyPair as CoconutKeyPair; -use coconut_interface::{ - Attribute, BlindSignRequest, BlindedSignature, Parameters, VerificationKey, -}; -use config::defaults::VALIDATOR_API_VERSION; -use credentials::coconut::params::{ - ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm, -}; -use crypto::asymmetric::encryption; -use crypto::shared_key::new_ephemeral_shared_key; -use crypto::symmetric::stream_cipher; +use crate::coconut::helpers::accepted_vote_err; +use crate::support::storage::NymApiStorage; +use getset::{CopyGetters, Getters}; use keypair::KeyPair; -use validator_api_requests::coconut::{ +use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; -use validator_client::nymd::{Coin, Fee}; -use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES}; - -use getset::{CopyGetters, Getters}; +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 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; +use validator_client::nym_api::routes::{BANDWIDTH, COCONUT_ROUTES}; +use validator_client::nyxd::{Coin, Fee}; -use self::comm::APICommunicationChannel; +pub(crate) mod client; +pub(crate) mod comm; +mod deposit; +pub(crate) mod dkg; +pub(crate) mod error; +pub(crate) mod helpers; +pub(crate) mod keypair; +#[cfg(test)] +pub(crate) mod tests; pub struct State { client: Arc<dyn LocalClient + Send + Sync>, mix_denom: String, key_pair: KeyPair, comm_channel: Arc<dyn APICommunicationChannel + Send + Sync>, - storage: ValidatorApiStorage, + storage: NymApiStorage, rng: Arc<Mutex<OsRng>>, } @@ -61,7 +61,7 @@ impl State { mix_denom: String, key_pair: KeyPair, comm_channel: D, - storage: ValidatorApiStorage, + storage: NymApiStorage, ) -> Self where C: LocalClient + Send + Sync + 'static, @@ -100,16 +100,16 @@ impl State { let (keypair, shared_key) = { let mut rng = *self.rng.lock().await; new_ephemeral_shared_key::< - ValidatorApiCredentialEncryptionAlgorithm, - ValidatorApiCredentialHkdfAlgorithm, + NymApiCredentialEncryptionAlgorithm, + NymApiCredentialHkdfAlgorithm, _, >(&mut rng, remote_key) }; let chunk_data = signature.to_bytes(); - let zero_iv = stream_cipher::zero_iv::<ValidatorApiCredentialEncryptionAlgorithm>(); - let encrypted_data = stream_cipher::encrypt::<ValidatorApiCredentialEncryptionAlgorithm>( + let zero_iv = stream_cipher::zero_iv::<NymApiCredentialEncryptionAlgorithm>(); + let encrypted_data = stream_cipher::encrypt::<NymApiCredentialEncryptionAlgorithm>( &shared_key, &zero_iv, &chunk_data, @@ -135,8 +135,10 @@ impl State { } } - pub async fn verification_key(&self) -> Result<VerificationKey> { - self.comm_channel.aggregated_verification_key().await + pub async fn verification_key(&self, epoch_id: EpochId) -> Result<VerificationKey> { + self.comm_channel + .aggregated_verification_key(epoch_id) + .await } } @@ -169,7 +171,7 @@ impl InternalSignRequest { mix_denom: String, key_pair: KeyPair, comm_channel: D, - storage: ValidatorApiStorage, + storage: NymApiStorage, ) -> AdHoc where C: LocalClient + Send + Sync + 'static, @@ -179,15 +181,8 @@ impl InternalSignRequest { AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async { rocket.manage(state).mount( // this format! is so ugly... - format!( - "/{}/{}/{}", - VALIDATOR_API_VERSION, COCONUT_ROUTES, BANDWIDTH - ), - routes![ - post_blind_sign, - post_partial_bandwidth_credential, - verify_bandwidth_credential - ], + format!("/{}/{}/{}", NYM_API_VERSION, COCONUT_ROUTES, BANDWIDTH), + routes![post_blind_sign, verify_bandwidth_credential], ) }) } @@ -195,7 +190,7 @@ impl InternalSignRequest { fn blind_sign(request: InternalSignRequest, key_pair: &CoconutKeyPair) -> Result<BlindedSignature> { let params = Parameters::new(request.total_params())?; - Ok(coconut_interface::blind_sign( + Ok(nym_coconut_interface::blind_sign( ¶ms, &key_pair.secret_key(), request.blind_sign_request(), @@ -243,18 +238,6 @@ pub async fn post_blind_sign( Ok(Json(response)) } -#[post("/partial-bandwidth-credential", data = "<tx_hash>")] -pub async fn post_partial_bandwidth_credential( - tx_hash: Json<String>, - state: &RocketState<State>, -) -> Result<Json<BlindedSignatureResponse>> { - let v = state - .signed_before(&tx_hash) - .await? - .ok_or(CoconutError::NoSignature)?; - Ok(Json(v)) -} - #[post("/verify-bandwidth-credential", data = "<verify_credential_body>")] pub async fn verify_bandwidth_credential( verify_credential_body: Json<VerifyCredentialBody>, @@ -290,7 +273,9 @@ pub async fn verify_bandwidth_credential( status: format!("{:?}", credential_status), }); } - let verification_key = state.verification_key().await?; + let verification_key = state + .verification_key(*verify_credential_body.credential().epoch_id()) + .await?; let mut vote_yes = verify_credential_body .credential() .verify(&verification_key); @@ -302,7 +287,7 @@ pub async fn verify_bandwidth_credential( ); // Vote yes or no on the proposal based on the verification result - state + let ret = state .client .vote_proposal( proposal_id, @@ -313,7 +298,8 @@ pub async fn verify_bandwidth_credential( Some(verify_credential_body.gateway_cosmos_addr().to_owned()), )), ) - .await?; + .await; + accepted_vote_err(ret)?; Ok(Json(VerifyCredentialResponse::new(vote_yes))) } diff --git a/validator-api/src/coconut/tests.rs b/nym-api/src/coconut/tests.rs similarity index 84% rename from validator-api/src/coconut/tests.rs rename to nym-api/src/coconut/tests.rs index cc75a112b2..065c7ac5a5 100644 --- a/validator-api/src/coconut/tests.rs +++ b/nym-api/src/coconut/tests.rs @@ -3,49 +3,51 @@ use super::InternalSignRequest; use crate::coconut::error::{CoconutError, Result}; -use coconut_bandwidth_contract_common::events::{ +use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, WasmMsg}; +use nym_api_requests::coconut::{ + BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, +}; +use nym_coconut_bandwidth_contract_common::events::{ DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO, DEPOSIT_VALUE, }; -use coconut_bandwidth_contract_common::spend_credential::{ +use nym_coconut_bandwidth_contract_common::spend_credential::{ SpendCredential, SpendCredentialResponse, }; -use coconut_interface::{hash_to_scalar, Credential, VerificationKey}; -use config::defaults::VOUCHER_INFO; -use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, WasmMsg}; -use credentials::coconut::bandwidth::BandwidthVoucher; -use credentials::coconut::params::{ - ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm, +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 crypto::shared_key::recompute_shared_key; -use crypto::symmetric::stream_cipher; +use nym_crypto::shared_key::recompute_shared_key; +use nym_crypto::symmetric::stream_cipher; use nymcoconut::tests::helpers::theta_from_keys_and_attributes; use nymcoconut::{ prepare_blind_sign, ttp_keygen, Base58, BlindSignRequest, BlindedSignature, Parameters, }; -use validator_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, -}; -use validator_client::nymd::Coin; -use validator_client::nymd::{tx::Hash, AccountId, DeliverTx, Event, Fee, Tag, TxResponse}; -use validator_client::validator_api::routes::{ - API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL, - COCONUT_ROUTES, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL, +use validator_client::nym_api::routes::{ + API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_ROUTES, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL, }; +use validator_client::nyxd::Coin; +use validator_client::nyxd::{tx::Hash, AccountId, DeliverTx, Event, Fee, Tag, TxResponse}; use crate::coconut::State; -use crate::ValidatorApiStorage; +use crate::support::storage::NymApiStorage; use async_trait::async_trait; -use coconut_dkg_common::dealer::{ +use cw3::ProposalResponse; +use cw4::MemberResponse; +use nym_coconut_dkg_common::dealer::{ ContractDealing, DealerDetails, DealerDetailsResponse, DealerType, }; -use coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX}; -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState, TOTAL_DEALINGS}; -use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; -use contracts_common::dealings::ContractSafeBytes; -use crypto::asymmetric::{encryption, identity}; -use cw3::ProposalResponse; -use dkg::Threshold; +use nym_coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX}; +use nym_coconut_dkg_common::types::{ + EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, TOTAL_DEALINGS, +}; +use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; +use nym_contracts_common::dealings::ContractSafeBytes; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_dkg::Threshold; use rand_07::rngs::OsRng; use rand_07::Rng; use rocket::http::Status; @@ -53,8 +55,8 @@ use rocket::local::asynchronous::Client; use std::collections::HashMap; use std::str::FromStr; use std::sync::{Arc, RwLock}; -use validator_client::nymd::cosmwasm_client::logs::Log; -use validator_client::nymd::cosmwasm_client::types::ExecuteResult; +use validator_client::nyxd::cosmwasm_client::logs::Log; +use validator_client::nyxd::cosmwasm_client::types::ExecuteResult; const TEST_COIN_DENOM: &str = "unym"; const TEST_REWARDING_VALIDATOR_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; @@ -66,11 +68,13 @@ pub(crate) struct DummyClient { proposal_db: Arc<RwLock<HashMap<u64, ProposalResponse>>>, spent_credential_db: Arc<RwLock<HashMap<String, SpendCredentialResponse>>>, - epoch_state: Arc<RwLock<EpochState>>, + epoch: Arc<RwLock<Epoch>>, dealer_details: Arc<RwLock<HashMap<String, DealerDetails>>>, threshold: Arc<RwLock<Option<Threshold>>>, dealings: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>, verification_share: Arc<RwLock<HashMap<String, ContractVKShare>>>, + group_db: Arc<RwLock<HashMap<String, MemberResponse>>>, + initial_dealers_db: Arc<RwLock<Option<InitialReplacementData>>>, } impl DummyClient { @@ -80,11 +84,13 @@ impl DummyClient { tx_db: Arc::new(RwLock::new(HashMap::new())), proposal_db: Arc::new(RwLock::new(HashMap::new())), spent_credential_db: Arc::new(RwLock::new(HashMap::new())), - epoch_state: Arc::new(RwLock::new(EpochState::default())), + epoch: Arc::new(RwLock::new(Epoch::default())), dealer_details: Arc::new(RwLock::new(HashMap::new())), threshold: Arc::new(RwLock::new(None)), dealings: Arc::new(RwLock::new(HashMap::new())), verification_share: Arc::new(RwLock::new(HashMap::new())), + group_db: Arc::new(RwLock::new(HashMap::new())), + initial_dealers_db: Arc::new(RwLock::new(None)), } } @@ -109,8 +115,8 @@ impl DummyClient { self } - pub fn _with_epoch_state(mut self, epoch_state: &Arc<RwLock<EpochState>>) -> Self { - self.epoch_state = Arc::clone(epoch_state); + pub fn _with_epoch(mut self, epoch: &Arc<RwLock<Epoch>>) -> Self { + self.epoch = Arc::clone(epoch); self } @@ -142,6 +148,22 @@ impl DummyClient { self.verification_share = Arc::clone(verification_share); self } + + pub fn _with_group_db( + mut self, + group_db: &Arc<RwLock<HashMap<String, MemberResponse>>>, + ) -> Self { + self.group_db = Arc::clone(group_db); + self + } + + pub fn with_initial_dealers_db( + mut self, + initial_dealers: &Arc<RwLock<Option<InitialReplacementData>>>, + ) -> Self { + self.initial_dealers_db = Arc::clone(initial_dealers); + self + } } #[async_trait] @@ -188,14 +210,28 @@ impl super::client::Client for DummyClient { }) } - async fn get_current_epoch_state(&self) -> Result<EpochState> { - Ok(*self.epoch_state.read().unwrap()) + async fn get_current_epoch(&self) -> Result<Epoch> { + Ok(*self.epoch.read().unwrap()) + } + + async fn group_member(&self, addr: String) -> Result<MemberResponse> { + Ok(self + .group_db + .read() + .unwrap() + .get(&addr) + .cloned() + .unwrap_or(MemberResponse { weight: None })) } async fn get_current_epoch_threshold(&self) -> Result<Option<Threshold>> { Ok(*self.threshold.read().unwrap()) } + async fn get_initial_dealers(&self) -> Result<Option<InitialReplacementData>> { + Ok(self.initial_dealers_db.read().unwrap().clone()) + } + async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse> { Ok(DealerDetailsResponse { details: self @@ -231,7 +267,10 @@ impl super::client::Client for DummyClient { .collect()) } - async fn get_verification_key_shares(&self) -> Result<Vec<ContractVKShare>> { + async fn get_verification_key_shares( + &self, + _epoch_id: EpochId, + ) -> Result<Vec<ContractVKShare>> { Ok(self .verification_share .read() @@ -273,21 +312,33 @@ impl super::client::Client for DummyClient { Ok(()) } + async fn advance_epoch_state(&self) -> Result<()> { + todo!() + } + async fn register_dealer( &self, bte_public_key_with_proof: EncodedBTEPublicKeyWithProof, announce_address: String, + _resharing: bool, ) -> Result<ExecuteResult> { - let assigned_index = OsRng.gen(); - self.dealer_details.write().unwrap().insert( - self.validator_address.to_string(), - DealerDetails { - address: Addr::unchecked(self.validator_address.to_string()), - bte_public_key_with_proof, - announce_address, - assigned_index, - }, - ); + let mut dealer_details = self.dealer_details.write().unwrap(); + let assigned_index = + if let Some(details) = dealer_details.get(self.validator_address.as_ref()) { + details.assigned_index + } else { + let assigned_index = OsRng.gen(); + dealer_details.insert( + self.validator_address.to_string(), + DealerDetails { + address: Addr::unchecked(self.validator_address.to_string()), + bte_public_key_with_proof, + announce_address, + assigned_index, + }, + ); + assigned_index + }; Ok(ExecuteResult { logs: vec![Log { msg_index: 0, @@ -300,7 +351,11 @@ impl super::client::Client for DummyClient { }) } - async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result<ExecuteResult> { + async fn submit_dealing( + &self, + dealing_bytes: ContractSafeBytes, + _resharing: bool, + ) -> Result<ExecuteResult> { self.dealings .write() .unwrap() @@ -323,6 +378,7 @@ impl super::client::Client for DummyClient { async fn submit_verification_key_share( &self, share: VerificationKeyShare, + resharing: bool, ) -> Result<ExecuteResult> { let dealer_details = self .dealer_details @@ -338,13 +394,16 @@ impl super::client::Client for DummyClient { announce_address: dealer_details.announce_address.clone(), node_index: dealer_details.assigned_index, owner: Addr::unchecked(self.validator_address.to_string()), + epoch_id: 0, verified: false, }, ); let proposal_id = OsRng.gen(); - let verify_vk_share_req = coconut_dkg_common::msg::ExecuteMsg::VerifyVerificationKeyShare { - owner: Addr::unchecked(self.validator_address.as_ref()), - }; + let verify_vk_share_req = + nym_coconut_dkg_common::msg::ExecuteMsg::VerifyVerificationKeyShare { + owner: Addr::unchecked(self.validator_address.as_ref()), + resharing, + }; let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: String::new(), msg: to_binary(&verify_vk_share_req).unwrap(), @@ -394,7 +453,7 @@ impl DummyCommunicationChannel { #[async_trait] impl super::comm::APICommunicationChannel for DummyCommunicationChannel { - async fn aggregated_verification_key(&self) -> Result<VerificationKey> { + async fn aggregated_verification_key(&self, _epoch_id: EpochId) -> Result<VerificationKey> { Ok(self.aggregated_verification_key.clone()) } } @@ -456,21 +515,21 @@ async fn signed_before() { 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 = ValidatorApiStorage::init(db_dir).await.unwrap(); + 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 nymd_client = + 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(key_pair).await; + staged_key_pair.set(Some(key_pair)).await; let rocket = rocket::build().attach(InternalSignRequest::stage( - nymd_client, + nyxd_client, TEST_COIN_DENOM.to_string(), staged_key_pair, comm_channel, @@ -526,18 +585,18 @@ async fn signed_before() { #[tokio::test] async fn state_functions() { - let nymd_client = + let nyxd_client = DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()); let params = Parameters::new(4).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 = ValidatorApiStorage::init(db_dir).await.unwrap(); + let storage = NymApiStorage::init(db_dir).await.unwrap(); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); let staged_key_pair = crate::coconut::KeyPair::new(); - staged_key_pair.set(key_pair).await; + staged_key_pair.set(Some(key_pair)).await; let state = State::new( - nymd_client, + nyxd_client, TEST_COIN_DENOM.to_string(), staged_key_pair, comm_channel, @@ -564,7 +623,7 @@ async fn state_functions() { expected_response.to_bytes() ); - let encryption_keypair = crypto::asymmetric::encryption::KeyPair::new(&mut OsRng); + 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, @@ -598,14 +657,14 @@ async fn state_functions() { .await .unwrap(); let remote_key = - crypto::asymmetric::encryption::PublicKey::from_bytes(&response.remote_key).unwrap(); + nym_crypto::asymmetric::encryption::PublicKey::from_bytes(&response.remote_key).unwrap(); let encryption_key = recompute_shared_key::< - ValidatorApiCredentialEncryptionAlgorithm, - ValidatorApiCredentialHkdfAlgorithm, + NymApiCredentialEncryptionAlgorithm, + NymApiCredentialHkdfAlgorithm, >(&remote_key, encryption_keypair.private_key()); - let zero_iv = stream_cipher::zero_iv::<ValidatorApiCredentialEncryptionAlgorithm>(); - let blinded_signature_bytes = stream_cipher::decrypt::<ValidatorApiCredentialEncryptionAlgorithm>( + let zero_iv = stream_cipher::zero_iv::<NymApiCredentialEncryptionAlgorithm>(); + let blinded_signature_bytes = stream_cipher::decrypt::<NymApiCredentialEncryptionAlgorithm>( &encryption_key, &zero_iv, &response.encrypted_signature, @@ -665,7 +724,7 @@ async fn blind_sign_correct() { 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 = ValidatorApiStorage::init(db_dir).await.unwrap(); + 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()); @@ -699,15 +758,15 @@ async fn blind_sign_correct() { .write() .unwrap() .insert(tx_hash.to_string(), tx_entry.clone()); - let nymd_client = + 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(key_pair).await; + staged_key_pair.set(Some(key_pair)).await; let rocket = rocket::build().attach(InternalSignRequest::stage( - nymd_client, + nyxd_client, TEST_COIN_DENOM.to_string(), staged_key_pair, comm_channel, @@ -768,83 +827,13 @@ async fn blind_sign_correct() { assert!(blinded_signature_response.is_ok()); } -#[tokio::test] -async fn signature_test() { - let tx_hash = String::from("7C41AF8266D91DE55E1C8F4712E6A952A165ED3D8C27C7B00428CBD0DE00A52B"); - let params = Parameters::new(4).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 = ValidatorApiStorage::init(db_dir).await.unwrap(); - let nymd_client = - DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()); - let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); - let staged_key_pair = crate::coconut::KeyPair::new(); - staged_key_pair.set(key_pair).await; - - let rocket = rocket::build().attach(InternalSignRequest::stage( - nymd_client, - TEST_COIN_DENOM.to_string(), - staged_key_pair, - comm_channel, - storage.clone(), - )); - let client = Client::tracked(rocket) - .await - .expect("valid rocket instance"); - - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL - )) - .json(&tx_hash) - .dispatch() - .await; - assert_eq!(response.status(), Status::BadRequest); - assert_eq!( - response.into_string().await.unwrap(), - CoconutError::NoSignature.to_string() - ); - - 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, &expected_response.to_base58_string()) - .await - .unwrap(); - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL - )) - .json(&tx_hash) - .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 - // let blinded_signature_response = response - // .into_json::<BlindedSignatureResponse>() - // .await - // .unwrap(); - let blinded_signature_response = - serde_json::from_str::<BlindedSignatureResponse>(&response.into_string().await.unwrap()) - .unwrap(); - assert_eq!( - blinded_signature_response.to_bytes(), - expected_response.to_bytes() - ); -} - #[tokio::test] async fn verification_of_bandwidth_credential() { // Setup variables let validator_address = AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(); let proposal_db = Arc::new(RwLock::new(HashMap::new())); let spent_credential_db = Arc::new(RwLock::new(HashMap::new())); - let nymd_client = DummyClient::new(validator_address.clone()) + let nyxd_client = DummyClient::new(validator_address.clone()) .with_proposal_db(&proposal_db) .with_spent_credential_db(&spent_credential_db); let mut db_dir = std::env::temp_dir(); @@ -865,12 +854,12 @@ async fn verification_of_bandwidth_credential() { theta_from_keys_and_attributes(¶ms, &key_pairs, &indices, &public_attributes).unwrap(); let key_pair = key_pairs.remove(0); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); - let storage1 = ValidatorApiStorage::init(db_dir).await.unwrap(); + let storage1 = NymApiStorage::init(db_dir).await.unwrap(); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); let staged_key_pair = crate::coconut::KeyPair::new(); - staged_key_pair.set(key_pair).await; + staged_key_pair.set(Some(key_pair)).await; let rocket = rocket::build().attach(InternalSignRequest::stage( - nymd_client.clone(), + nyxd_client.clone(), TEST_COIN_DENOM.to_string(), staged_key_pair, comm_channel.clone(), @@ -881,7 +870,7 @@ async fn verification_of_bandwidth_credential() { .await .expect("valid rocket instance"); - let credential = Credential::new(4, theta.clone(), voucher_value, voucher_info.to_string()); + let credential = Credential::new(4, theta.clone(), voucher_value, voucher_info.to_string(), 0); let proposal_id = 42; // The address is not used, so we can use a duplicate let gateway_cosmos_addr = validator_address.clone(); @@ -966,7 +955,7 @@ async fn verification_of_bandwidth_credential() { // Test the endpoint without any credential recorded in the Coconut Bandwidth Contract let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); - let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { funds: funds.clone().into(), }; let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -1032,6 +1021,7 @@ async fn verification_of_bandwidth_credential() { theta.clone(), voucher_value, String::from("bad voucher info"), + 0, ); let bad_req = VerifyCredentialBody::new(bad_credential, proposal_id, gateway_cosmos_addr.clone()); @@ -1061,7 +1051,7 @@ async fn verification_of_bandwidth_credential() { // Test the endpoint with a proposal that has a different value for the funds to be released // then what's in the credential let funds = Coin::new((voucher_value + 10) as u128, TEST_COIN_DENOM); - let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { funds: funds.clone().into(), }; let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -1099,7 +1089,7 @@ async fn verification_of_bandwidth_credential() { // Test the endpoint with every dependency met let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); - let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { funds: funds.clone().into(), }; let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { diff --git a/validator-api/src/epoch_operations/error.rs b/nym-api/src/epoch_operations/error.rs similarity index 55% rename from validator-api/src/epoch_operations/error.rs rename to nym-api/src/epoch_operations/error.rs index bb94df5f49..a57e0153fc 100644 --- a/validator-api/src/epoch_operations/error.rs +++ b/nym-api/src/epoch_operations/error.rs @@ -1,21 +1,35 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crate::node_status_api::models::ValidatorApiStorageError; +use crate::node_status_api::models::NymApiStorageError; +use nym_mixnet_contract_common::EpochState; use thiserror::Error; -use validator_client::nymd::error::NymdError; +use validator_client::nyxd::error::NyxdError; +use validator_client::nyxd::AccountId; use validator_client::ValidatorClientError; #[derive(Debug, Error)] pub enum RewardingError { + #[error("Our account ({our_address}) is not permitted to update rewarded set and perform rewarding. The allowed address is {allowed_address}")] + Unauthorised { + our_address: AccountId, + allowed_address: AccountId, + }, + + #[error("the current epoch is in the wrong state ({current_state}) to perform the requested operation: {operation}")] + InvalidEpochState { + current_state: EpochState, + operation: String, + }, + // #[error("There were no mixnodes to reward (network is dead)")] // NoMixnodesToReward, #[error("Failed to execute the smart contract - {0}")] - ContractExecutionFailure(NymdError), + ContractExecutionFailure(NyxdError), // The inner error should be modified at some point... #[error("We run into storage issues - {0}")] - StorageError(ValidatorApiStorageError), + StorageError(NymApiStorageError), #[error("Failed to query the smart contract - {0}")] ValidatorClientError(ValidatorClientError), @@ -32,14 +46,14 @@ pub enum RewardingError { }, } -impl From<NymdError> for RewardingError { - fn from(err: NymdError) -> Self { +impl From<NyxdError> for RewardingError { + fn from(err: NyxdError) -> Self { RewardingError::ContractExecutionFailure(err) } } -impl From<ValidatorApiStorageError> for RewardingError { - fn from(err: ValidatorApiStorageError) -> Self { +impl From<NymApiStorageError> for RewardingError { + fn from(err: NymApiStorageError) -> Self { RewardingError::StorageError(err) } } diff --git a/nym-api/src/epoch_operations/event_reconciliation.rs b/nym-api/src/epoch_operations/event_reconciliation.rs new file mode 100644 index 0000000000..0279f96b21 --- /dev/null +++ b/nym-api/src/epoch_operations/event_reconciliation.rs @@ -0,0 +1,70 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::epoch_operations::error::RewardingError; +use crate::RewardedSetUpdater; +use nym_mixnet_contract_common::EpochState; +use std::cmp::max; + +impl RewardedSetUpdater { + pub(super) async fn reconcile_epoch_events(&self) -> Result<(), RewardingError> { + let epoch_status = self.nyxd_client.get_current_epoch_status().await?; + match epoch_status.state { + state @ EpochState::InProgress | state @ EpochState::Rewarding { .. } => { + // hard error, this shouldn't have happened! + error!("tried to perform node rewarding while in {state} state!"); + Err(RewardingError::InvalidEpochState { + current_state: state, + operation: "reconciling epoch events".to_string(), + }) + } + EpochState::AdvancingEpoch => { + warn!("we seem to have crashed mid epoch operations... no need to reconcile events as we've already done that!"); + Ok(()) + } + EpochState::ReconcilingEvents => { + log::info!("Reconciling all pending epoch events..."); + if let Err(err) = self._reconcile_epoch_events().await { + log::error!("FAILED to reconcile epoch events... - {err}"); + Err(err) + } else { + log::info!("Reconciled all pending epoch events... SUCCESS"); + Ok(()) + } + } + } + } + + async fn _reconcile_epoch_events(&self) -> Result<(), RewardingError> { + let pending_events = self.nyxd_client.get_pending_events_count().await?; + + // be conservative about number of events we resolve at once. + // contract should be able to handle few hundred at once + // but keep it on the lower side. + let limit = 100; + + let mut required_calls = pending_events / limit; + if pending_events % limit != 0 { + required_calls += 1; + } + + // make sure to at least call it once so that if there are no pending events, + // the epoch would get advanced into the next phase + for _ in 0..max(required_calls, 1) { + self.nyxd_client.reconcile_epoch_events(Some(limit)).await?; + } + + // in the incredibly unlikely/borderline impossible scenario a HUGE number of events got pushed + // while we were reconciling the events, do it one more time + // + // note: it's perfectly fine if we don't clear EXACTLY everything, + // since when we execute transaction to actually advance the epoch, + // it will resolve all remaining events. + let pending_events = self.nyxd_client.get_pending_events_count().await?; + if pending_events > 20 { + self.nyxd_client.reconcile_epoch_events(Some(limit)).await?; + } + + Ok(()) + } +} diff --git a/validator-api/src/epoch_operations/helpers.rs b/nym-api/src/epoch_operations/helpers.rs similarity index 100% rename from validator-api/src/epoch_operations/helpers.rs rename to nym-api/src/epoch_operations/helpers.rs diff --git a/nym-api/src/epoch_operations/mod.rs b/nym-api/src/epoch_operations/mod.rs new file mode 100644 index 0000000000..89daaebb4d --- /dev/null +++ b/nym-api/src/epoch_operations/mod.rs @@ -0,0 +1,293 @@ +// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +// 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 +// rather than attempting to change global view (i.e. the active set) +// +// 2. However, even if it was to exist in the nym contract cache refresher, we'd have to create a different "run" +// method as it doesn't have access to the signing client which we need in the case of updating rewarded sets +// (because nym contract cache can be run by anyone regardless of whether, say, network monitor exists) +// +// 3. Eventually this whole procedure is going to get expanded to allow for distribution of rewarded set generation +// and hence this might be a good place for it. + +use crate::node_status_api::ONE_DAY; +use crate::nym_contract_cache::cache::NymContractCache; +use crate::support::nyxd::Client; +use crate::support::storage::NymApiStorage; +use error::RewardingError; +use nym_mixnet_contract_common::{CurrentIntervalResponse, Interval}; +use nym_task::{TaskClient, TaskManager}; +pub(crate) use rewarding::MixnodeToReward; +use std::collections::HashSet; +use std::time::Duration; +use tokio::time::sleep; + +pub(crate) mod error; +mod event_reconciliation; +mod helpers; +mod rewarded_set_assignment; +mod rewarding; +mod transition_beginning; + +pub struct RewardedSetUpdater { + nyxd_client: Client, + nym_contract_cache: NymContractCache, + storage: NymApiStorage, +} + +impl RewardedSetUpdater { + pub(crate) async fn current_interval_details( + &self, + ) -> Result<CurrentIntervalResponse, RewardingError> { + Ok(self.nyxd_client.get_current_interval().await?) + } + + pub(crate) fn new( + nyxd_client: Client, + nym_contract_cache: NymContractCache, + storage: NymApiStorage, + ) -> Self { + RewardedSetUpdater { + nyxd_client, + nym_contract_cache, + storage, + } + } + + // This is where the epoch gets advanced, and all epoch related transactions originate + /// Upon each epoch having finished the following actions are executed by this nym-api: + /// 1. it queries the mixnet contract to check the current `EpochState` in order to figure out whether + /// a different nym-api has already started epoch transition (not yet applicable) + /// 2. it sends a `BeginEpochTransition` message to the mixnet contract causing the following to happen: + /// - if successful, the address of the this validator is going to be saved as being responsible for progressing this epoch. + /// What it means in practice is that once we have multiple instances of nym-api running, + /// only this one will try to perform the rest of the actions. It will also allow it to + /// more easily recover in case of crashes. + /// - the `EpochState` changes to `Rewarding`, meaning the nym-api will now be allowed to send + /// `RewardMixnode` transactions. However, it's not going to be able anything else like `ReconcileEpochEvents` + /// until that is done. + /// - ability to send transactions (by other users) that get resolved once given epoch/interval rolls over, + /// such as `BondMixnode` or `DelegateToMixnode` will temporarily be frozen until the entire procedure is finished. + /// 3. it obtains the current rewarded set and for each node in there (**SORTED BY MIX_ID!!**), + /// it sends (in a single batch) `RewardMixnode` message with the measured performance. + /// Once the final message gets executed, the mixnet contract automatically transitions + /// the state to `ReconcilingEvents`. + /// 4. it obtains the number of pending epoch and interval events and repeatedly sends + /// `ReconcileEpochEvents` transaction until all of them are resolved. + /// At this point the mixnet contract automatically transitions the state to `AdvancingEpoch`. + /// 5. it obtains the list of all nodes on the network and pseudorandomly (but weighted by total stake) + /// determines the new rewarded set. It then assigns layers to the provided nodes taking + /// family information into consideration. Finally it sends `AdvanceCurrentEpoch` message + /// containing the set and layer information thus rolling over the epoch and changing the state + /// to `InProgress`. + /// 6. it purges old (older than 48h) measurement data + /// 7. the whole process repeats once the new epoch finishes + async fn perform_epoch_operations(&self, interval: Interval) -> Result<(), RewardingError> { + log::info!("The current epoch has finished."); + log::info!( + "Interval id: {}, epoch id: {} (absolute epoch id: {})", + interval.current_interval_id(), + interval.current_epoch_id(), + interval.current_epoch_absolute_id() + ); + log::info!( + "The current epoch has lasted from {} until {}", + interval.current_epoch_start(), + interval.current_epoch_end() + ); + + log::info!("Performing all epoch operations..."); + + let epoch_end = interval.current_epoch_end(); + + let all_mixnodes = self.nym_contract_cache.mixnodes_filtered().await; + if all_mixnodes.is_empty() { + // that's a bit weird, but + log::warn!("there don't seem to be any mixnodes on the network!") + } + + let epoch_status = self.nyxd_client.get_current_epoch_status().await?; + if !epoch_status.is_in_progress() { + if epoch_status.being_advanced_by.as_str() + != self.nyxd_client.client_address().await.as_ref() + { + // another nym-api is already handling + error!("another nym-api ({}) is already advancing the epoch... but we shouldn't have other nym-apis yet!", epoch_status.being_advanced_by); + return Ok(()); + } else { + warn!("we seem to have crashed mid-epoch advancement..."); + } + } else { + let should_continue = self.begin_epoch_transition().await?; + if !should_continue { + return Ok(()); + } + } + + // Reward all the nodes in the still current, soon to be previous rewarded set + //log::info!("Rewarding the current rewarded set..."); + //self.reward_current_rewarded_set(interval).await?; + + // note: those operations don't really have to be atomic, so it's fine to send them + // as separate transactions + self.reconcile_epoch_events().await?; + self.update_rewarded_set_and_advance_epoch(&all_mixnodes) + .await?; + + log::info!("Purging old node statuses from the storage..."); + let cutoff = (epoch_end - 2 * ONE_DAY).unix_timestamp(); + self.storage.purge_old_statuses(cutoff).await?; + + Ok(()) + } + + async fn update_blacklist(&mut self, interval: &Interval) -> Result<(), RewardingError> { + info!("Updating blacklists"); + + let mut mix_blacklist_add = HashSet::new(); + let mut mix_blacklist_remove = HashSet::new(); + let mut gate_blacklist_add = HashSet::new(); + let mut gate_blacklist_remove = HashSet::new(); + + let mixnodes = self + .storage + .get_all_avg_mix_reliability_in_last_24hr(interval.current_epoch_end_unix_timestamp()) + .await?; + let gateways = self + .storage + .get_all_avg_gateway_reliability_in_last_24hr( + interval.current_epoch_end_unix_timestamp(), + ) + .await?; + + // TODO: Make thresholds configurable + for mix in mixnodes { + if mix.value() <= 50.0 { + mix_blacklist_add.insert(mix.mix_id()); + } else { + mix_blacklist_remove.insert(mix.mix_id()); + } + } + + self.nym_contract_cache + .update_mixnodes_blacklist(mix_blacklist_add, mix_blacklist_remove) + .await; + + for gateway in gateways { + if gateway.value() <= 50.0 { + gate_blacklist_add.insert(gateway.identity().to_string()); + } else { + gate_blacklist_remove.insert(gateway.identity().to_string()); + } + } + + self.nym_contract_cache + .update_gateways_blacklist(gate_blacklist_add, gate_blacklist_remove) + .await; + Ok(()) + } + + async fn wait_until_epoch_end(&mut self, shutdown: &mut TaskClient) -> Option<Interval> { + const POLL_INTERVAL: Duration = Duration::from_secs(120); + + loop { + let current_interval = match self.current_interval_details().await { + Err(err) => { + error!("failed to obtain information about the current interval - {err}. Going to retry in {}s", POLL_INTERVAL.as_secs()); + tokio::select! { + _ = sleep(POLL_INTERVAL) => { + continue + }, + _ = shutdown.recv() => { + trace!("wait_until_epoch_end: Received shutdown"); + break None + } + } + } + Ok(interval) => interval, + }; + + if current_interval.is_current_epoch_over { + return Some(current_interval.interval); + } else { + let time_left = current_interval.time_until_current_epoch_end(); + log::info!( + "Waiting for epoch change, it should take approximately {}s", + time_left.as_secs() + ); + let wait_time = if time_left < POLL_INTERVAL { + // add few seconds to adjust for possible block time drift + time_left + Duration::from_secs(10) + } else { + POLL_INTERVAL + }; + + tokio::select! { + _ = sleep(wait_time) => { + + }, + _ = shutdown.recv() => { + trace!("wait_until_epoch_end: Received shutdown"); + break None + } + } + } + } + } + + pub(crate) async fn run(&mut self, mut shutdown: TaskClient) -> Result<(), RewardingError> { + self.nym_contract_cache.wait_for_initial_values().await; + + while !shutdown.is_shutdown() { + let interval_details = match self.wait_until_epoch_end(&mut shutdown).await { + // received a shutdown + None => return Ok(()), + Some(interval) => interval, + }; + if let Err(err) = self.update_blacklist(&interval_details).await { + error!("failed to update the node blacklist - {err}"); + continue; + } + if let Err(err) = self.perform_epoch_operations(interval_details).await { + error!("failed to perform epoch operations - {err}"); + sleep(Duration::from_secs(30)).await; + } + } + + Ok(()) + } + + pub(crate) fn start( + nyxd_client: Client, + nym_contract_cache: &NymContractCache, + storage: &NymApiStorage, + shutdown: &TaskManager, + ) { + let mut rewarded_set_updater = RewardedSetUpdater::new( + nyxd_client, + nym_contract_cache.to_owned(), + storage.to_owned(), + ); + let shutdown_listener = shutdown.subscribe(); + tokio::spawn(async move { rewarded_set_updater.run(shutdown_listener).await }); + } +} + +// before going any further, let's check whether we're allowed to perform rewarding +// (if not, let's blow up sooner rather than later) +pub(crate) async fn ensure_rewarding_permission( + nyxd_client: &Client, +) -> Result<(), RewardingError> { + let allowed_address = nyxd_client.get_rewarding_validator_address().await?; + let our_address = nyxd_client.client_address().await; + if allowed_address != our_address { + Err(RewardingError::Unauthorised { + our_address, + allowed_address, + }) + } else { + Ok(()) + } +} diff --git a/nym-api/src/epoch_operations/rewarded_set_assignment.rs b/nym-api/src/epoch_operations/rewarded_set_assignment.rs new file mode 100644 index 0000000000..adb7508867 --- /dev/null +++ b/nym-api/src/epoch_operations/rewarded_set_assignment.rs @@ -0,0 +1,196 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::epoch_operations::error::RewardingError; +use crate::epoch_operations::helpers::stake_to_f64; +use crate::RewardedSetUpdater; +use nym_mixnet_contract_common::families::FamilyHead; +use nym_mixnet_contract_common::{EpochState, IdentityKey, Layer, LayerAssignment, MixNodeDetails}; +use rand::prelude::SliceRandom; +use rand::rngs::OsRng; +use std::collections::HashMap; + +impl RewardedSetUpdater { + // Needs to run for active and reserve sets separatley, as it does not preserve order + async fn determine_layers( + &self, + set: &[MixNodeDetails], + ) -> Result<Vec<LayerAssignment>, RewardingError> { + let mut assignments = Vec::with_capacity(set.len()); + let target_layer_count = set.len() / 3; + + let mix_to_family = self.nym_contract_cache.mix_to_family().await.to_vec(); + + let mix_to_family = mix_to_family + .into_iter() + .collect::<HashMap<IdentityKey, FamilyHead>>(); + + let mut regular_nodes = Vec::with_capacity(set.len()); + + let mut families = HashMap::new(); + + for node in set.iter() { + if let Some(fh) = mix_to_family.get(node.bond_information.identity()) { + let family: &mut Vec<u32> = families.entry(fh.identity()).or_default(); + family.push(node.mix_id()) + } else { + regular_nodes.push(node.mix_id()) + } + } + + let mut layers = HashMap::new(); + layers.insert(Layer::One, Vec::with_capacity(target_layer_count)); + layers.insert(Layer::Two, Vec::with_capacity(target_layer_count)); + layers.insert(Layer::Three, Vec::with_capacity(target_layer_count)); + + // Assign all members of a family to same layer + for (_head, members) in families.iter_mut() { + let smallest_layer = layers + .iter() + .min_by_key(|(_layer, members)| members.len()) + .map(|(layer, _members)| *layer) + .unwrap_or(Layer::One); + + let entry = layers.entry(smallest_layer).or_default(); + if entry.len() + members.len() <= target_layer_count { + entry.extend_from_slice(members) + } + } + + // Assign nodes with no families into layers + for mix_id in regular_nodes.drain(..) { + let smallest_layer = layers + .iter() + .min_by_key(|(_layer, members)| members.len()) + .map(|(layer, _members)| *layer) + .unwrap_or(Layer::One); + + let entry = layers.entry(smallest_layer).or_default(); + if entry.len() < target_layer_count { + entry.push(mix_id) + } + } + + for (layer, members) in layers { + let layer_assignments = members + .into_iter() + .map(|mix_id| LayerAssignment::new(mix_id, layer)); + assignments.extend(layer_assignments); + } + Ok(assignments) + } + + fn determine_rewarded_set( + &self, + mixnodes: &[MixNodeDetails], + nodes_to_select: u32, + ) -> Result<Vec<MixNodeDetails>, RewardingError> { + if mixnodes.is_empty() { + return Ok(Vec::new()); + } + + let mut rng = OsRng; + + // generate list of mixnodes and their relatively weight (by total stake) + let choices = mixnodes + .iter() + .map(|mix| { + let total_stake = stake_to_f64(mix.total_stake()); + (mix.to_owned(), total_stake) + }) + .collect::<Vec<_>>(); + + // the unwrap here is fine as an error can only be thrown under one of the following conditions: + // - our mixnode list is empty - we have already checked for that + // - we have invalid weights, i.e. less than zero or NaNs - it shouldn't happen in our case as we safely cast down from u128 + // - all weights are zero - it's impossible in our case as the list of nodes is not empty and weight is proportional to stake. You must have non-zero stake in order to bond + // - we have more than u32::MAX values (which is incredibly unrealistic to have 4B mixnodes bonded... literally every other person on the planet would need one) + Ok(choices + .choose_multiple_weighted(&mut rng, nodes_to_select as usize, |item| item.1)? + .map(|(mix, _weight)| mix.to_owned()) + .collect()) + } + + pub(super) async fn update_rewarded_set_and_advance_epoch( + &self, + all_mixnodes: &[MixNodeDetails], + ) -> Result<(), RewardingError> { + let epoch_status = self.nyxd_client.get_current_epoch_status().await?; + match epoch_status.state { + EpochState::AdvancingEpoch => { + log::info!("Advancing epoch and updating the rewarded set..."); + if let Err(err) = self + ._update_rewarded_set_and_advance_epoch(all_mixnodes) + .await + { + log::error!("FAILED to advance the current epoch... - {err}"); + Err(err) + } else { + log::info!("Advanced the epoch and updated the rewarded set... SUCCESS"); + Ok(()) + } + } + state => { + // hard error, this shouldn't have happened! + error!("tried to perform node rewarded set assignment while in {state} state!"); + Err(RewardingError::InvalidEpochState { + current_state: state, + operation: "assigning rewarded set".to_string(), + }) + } + } + } + + async fn _update_rewarded_set_and_advance_epoch( + &self, + all_mixnodes: &[MixNodeDetails], + ) -> Result<(), RewardingError> { + // we grab rewarding parameters here as they might have gotten updated when performing epoch actions + let rewarding_parameters = self.nyxd_client.get_current_rewarding_parameters().await?; + + debug!("Rewarding paremeters: {:?}", rewarding_parameters); + + let new_rewarded_set = + self.determine_rewarded_set(all_mixnodes, rewarding_parameters.rewarded_set_size)?; + + debug!("New rewarded set: {:?}", new_rewarded_set); + + let empty = vec![]; + + let (active_set, reserve_set) = if new_rewarded_set.len() + <= rewarding_parameters.active_set_size as usize + { + warn!("Active set size ({}) is greater then rewarded set len ({}), there will be no reserve set", rewarding_parameters.active_set_size, new_rewarded_set.len()); + (new_rewarded_set.as_slice(), empty.as_slice()) + } else { + new_rewarded_set.split_at(rewarding_parameters.active_set_size as usize) + }; + + let mut active_set_layer_assignments = self.determine_layers(active_set).await?; + debug!( + "Active set layer assignments: {:?}", + active_set_layer_assignments + ); + let reserve_set_layer_assignments = self.determine_layers(reserve_set).await?; + debug!( + "Reserve set layer assignments: {:?}", + reserve_set_layer_assignments + ); + + active_set_layer_assignments.extend(reserve_set_layer_assignments); + + debug!( + "Rewarded set layer assignments: {:?}", + active_set_layer_assignments + ); + + self.nyxd_client + .advance_current_epoch( + active_set_layer_assignments, + rewarding_parameters.active_set_size, + ) + .await?; + + Ok(()) + } +} diff --git a/nym-api/src/epoch_operations/rewarding.rs b/nym-api/src/epoch_operations/rewarding.rs new file mode 100644 index 0000000000..1630d369d7 --- /dev/null +++ b/nym-api/src/epoch_operations/rewarding.rs @@ -0,0 +1,133 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::epoch_operations::error::RewardingError; +use crate::support::storage::models::RewardingReport; +use crate::RewardedSetUpdater; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::{EpochState, ExecuteMsg, Interval, MixId}; + +#[derive(Debug, Clone, Copy)] +pub(crate) struct MixnodeToReward { + pub(crate) mix_id: MixId, + + pub(crate) performance: Performance, +} + +impl From<MixnodeToReward> for ExecuteMsg { + fn from(mix_reward: MixnodeToReward) -> Self { + ExecuteMsg::RewardMixnode { + mix_id: mix_reward.mix_id, + performance: mix_reward.performance, + } + } +} + +impl RewardedSetUpdater { + pub(super) async fn reward_current_rewarded_set( + &self, + current_interval: Interval, + ) -> Result<(), RewardingError> { + let epoch_status = self.nyxd_client.get_current_epoch_status().await?; + match epoch_status.state { + EpochState::InProgress => { + // hard error, this shouldn't have happened! + error!("tried to perform node rewarding while the epoch is still in progress!"); + Err(RewardingError::InvalidEpochState { + current_state: EpochState::InProgress, + operation: "mix rewarding".to_string(), + }) + } + EpochState::ReconcilingEvents | EpochState::AdvancingEpoch => { + warn!("we seem to have crashed mid epoch operations... no need to reward mixnodes as we've already done that! (or this could be a false positive if there were no nodes to reward - to fix this warning later)"); + Ok(()) + } + EpochState::Rewarding { .. } => { + log::info!("Rewarding the current rewarded set..."); + if let Err(err) = self._reward_current_rewarded_set(current_interval).await { + log::error!("FAILED to reward rewarded set - {err}"); + Err(err) + } else { + log::info!("Rewarded current rewarded set... SUCCESS"); + Ok(()) + } + } + } + } + + async fn _reward_current_rewarded_set( + &self, + current_interval: Interval, + ) -> Result<(), RewardingError> { + let mut to_reward = self.nodes_to_reward(current_interval).await; + to_reward.sort_by_key(|a| a.mix_id); + + if let Some(existing_report) = self + .storage + .get_rewarding_report(current_interval.current_epoch_absolute_id()) + .await? + { + warn!("We have already rewarded mixnodes for this rewarding epoch ({}). {} nodes should have gotten rewards", existing_report.absolute_epoch_id, existing_report.eligible_mixnodes); + return Ok(()); + } + + if to_reward.is_empty() { + info!("There are no nodes to reward in this epoch"); + } else if let Err(err) = self.nyxd_client.send_rewarding_messages(&to_reward).await { + error!( + "failed to perform mixnode rewarding for epoch {}! Error encountered: {err}", + current_interval.current_epoch_absolute_id(), + ); + return Err(err.into()); + } + + log::info!("rewarded {} mixnodes...", to_reward.len()); + + let rewarding_report = RewardingReport { + absolute_epoch_id: current_interval.current_epoch_absolute_id(), + eligible_mixnodes: to_reward.len() as u32, + }; + + self.storage + .insert_rewarding_report(rewarding_report) + .await?; + + Ok(()) + } + + async fn nodes_to_reward(&self, interval: Interval) -> Vec<MixnodeToReward> { + // try to get current up to date view of the network bypassing the cache + // in case the epochs were significantly shortened for the purposes of testing + let rewarded_set: Vec<MixId> = match self.nyxd_client.get_rewarded_set_mixnodes().await { + Ok(nodes) => nodes.into_iter().map(|(id, _)| id).collect::<Vec<_>>(), + Err(err) => { + warn!("failed to obtain the current rewarded set - {err}. falling back to the cached version"); + self.nym_contract_cache + .rewarded_set() + .await + .into_inner() + .into_iter() + .map(|node| node.mix_id()) + .collect::<Vec<_>>() + } + }; + + let mut eligible_nodes = Vec::with_capacity(rewarded_set.len()); + for mix_id in rewarded_set { + let uptime = self + .storage + .get_average_mixnode_uptime_in_the_last_24hrs( + mix_id, + interval.current_epoch_end_unix_timestamp(), + ) + .await + .unwrap_or_default(); + eligible_nodes.push(MixnodeToReward { + mix_id, + performance: uptime.into(), + }) + } + + eligible_nodes + } +} diff --git a/nym-api/src/epoch_operations/transition_beginning.rs b/nym-api/src/epoch_operations/transition_beginning.rs new file mode 100644 index 0000000000..4df4ed7dfe --- /dev/null +++ b/nym-api/src/epoch_operations/transition_beginning.rs @@ -0,0 +1,31 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::epoch_operations::error::RewardingError; +use crate::epoch_operations::RewardedSetUpdater; + +impl RewardedSetUpdater { + // returns boolean indicating whether we should bother continuing + pub(super) async fn begin_epoch_transition(&self) -> Result<bool, RewardingError> { + info!("starting the epoch transition..."); + if let Err(err) = self._begin_epoch_transition().await { + // perform the state query again to make sure it's not because other nym-api (not yet applicable) + // wasn't faster than us + let epoch_status = self.nyxd_client.get_current_epoch_status().await?; + if !epoch_status.is_in_progress() { + log::error!("FAILED to begin epoch progression: {err}"); + Err(err) + } else { + error!("another nym-api ({}) is already advancing the epoch... but we shouldn't have other nym-apis yet!", epoch_status.being_advanced_by); + Ok(false) + } + } else { + log::info!("Begun epoch transition... SUCCESS"); + Ok(true) + } + } + + async fn _begin_epoch_transition(&self) -> Result<(), RewardingError> { + Ok(self.nyxd_client.begin_epoch_transition().await?) + } +} diff --git a/nym-api/src/main.rs b/nym-api/src/main.rs new file mode 100644 index 0000000000..e0e862cf2d --- /dev/null +++ b/nym-api/src/main.rs @@ -0,0 +1,179 @@ +// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +#[macro_use] +extern crate rocket; + +use crate::epoch_operations::RewardedSetUpdater; +use crate::node_status_api::uptime_updater::HistoricalUptimeUpdater; +use crate::support::cli; +use crate::support::cli::CliArgs; +use crate::support::config::Config; +use crate::support::storage; +use crate::support::storage::NymApiStorage; +use ::nym_config::defaults::setup_env; +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::NymConfig; +use nym_contract_cache::cache::NymContractCache; +use nym_task::TaskManager; +use rand::rngs::OsRng; +use std::error::Error; +use support::{http, nyxd}; + +mod circulating_supply_api; +mod coconut; +mod epoch_operations; +mod network_monitor; +pub(crate) mod node_status_api; +pub(crate) mod nym_contract_cache; +pub(crate) mod support; + +struct ShutdownHandles { + task_manager_handle: TaskManager, + rocket_handle: rocket::Shutdown, +} + +#[tokio::main] +async fn main() -> Result<(), Box<dyn Error + Send + Sync>> { + println!("Starting nym api..."); + + cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] { + // instrument tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time + console_subscriber::init(); + }} + + setup_logging(); + let args = cli::CliArgs::parse(); + setup_env(args.config_env_file.as_ref()); + run_nym_api(args).await +} + +async fn start_nym_api_tasks( + config: Config, +) -> Result<ShutdownHandles, Box<dyn Error + Send + Sync>> { + let system_version = clap::crate_version!(); + + let nyxd_client = nyxd::Client::new(&config); + let mix_denom = nyxd_client.chain_details().await.mix_denom.base; + + let coconut_keypair = coconut::keypair::KeyPair::new(); + + // let's build our rocket! + let rocket = http::setup_rocket( + &config, + mix_denom, + nyxd_client.clone(), + coconut_keypair.clone(), + ) + .await?; + + // setup shutdowns + let shutdown = TaskManager::new(10); + + // Rocket handles shutdown on its own, but its shutdown handling should be incorporated + // with that of the rest of the tasks. Currently its runtime is forcefully terminated once + // nym-api exits. + let rocket_shutdown_handle = rocket.shutdown(); + + // get references to the managed state + let nym_contract_cache_state = rocket.state::<NymContractCache>().unwrap(); + let node_status_cache_state = rocket.state::<NodeStatusCache>().unwrap(); + let circulating_supply_cache_state = rocket.state::<CirculatingSupplyCache>().unwrap(); + let maybe_storage = rocket.state::<NymApiStorage>(); + + // start all the caches first + let nym_contract_cache_listener = nym_contract_cache::start_refresher( + &config, + nym_contract_cache_state, + nyxd_client.clone(), + &shutdown, + ); + node_status_api::start_cache_refresh( + &config, + nym_contract_cache_state, + node_status_cache_state, + maybe_storage, + nym_contract_cache_listener, + &shutdown, + ); + circulating_supply_api::start_cache_refresh( + &config, + nyxd_client.clone(), + circulating_supply_cache_state, + &shutdown, + ); + + // start dkg task + if config.get_coconut_signer_enabled() { + DkgController::start( + &config, + nyxd_client.clone(), + coconut_keypair, + OsRng, + &shutdown, + ) + .await?; + } + + // and then only start the uptime updater (and the monitor itself, duh) + // if the monitoring if it's enabled + if config.get_network_monitor_enabled() { + // if network monitor is enabled, the storage MUST BE available + let storage = maybe_storage.unwrap(); + + network_monitor::start( + &config, + nym_contract_cache_state, + storage, + nyxd_client.clone(), + system_version, + &shutdown, + ) + .await; + + HistoricalUptimeUpdater::start(storage, &shutdown); + + // start 'rewarding' if its enabled + if config.get_rewarding_enabled() { + epoch_operations::ensure_rewarding_permission(&nyxd_client).await?; + RewardedSetUpdater::start(nyxd_client, nym_contract_cache_state, storage, &shutdown); + } + } + + // Launch the rocket, serve http endpoints and finish the startup + tokio::spawn(rocket.launch()); + + Ok(ShutdownHandles { + task_manager_handle: shutdown, + rocket_handle: rocket_shutdown_handle, + }) +} + +async fn run_nym_api(cli_args: CliArgs) -> Result<(), Box<dyn Error + Send + Sync>> { + 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_file(None)?; + 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/validator-api/src/network_monitor/chunker.rs b/nym-api/src/network_monitor/chunker.rs similarity index 94% rename from validator-api/src/network_monitor/chunker.rs rename to nym-api/src/network_monitor/chunker.rs index 0caaba0811..2ac9fa04a0 100644 --- a/validator-api/src/network_monitor/chunker.rs +++ b/nym-api/src/network_monitor/chunker.rs @@ -1,14 +1,14 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use nymsphinx::forwarding::packet::MixPacket; -use nymsphinx::message::NymMessage; -use nymsphinx::{ +use nym_sphinx::forwarding::packet::MixPacket; +use nym_sphinx::message::NymMessage; +use nym_sphinx::{ acknowledgements::AckKey, addressing::clients::Recipient, preparer::MessagePreparer, }; +use nym_topology::NymTopology; use rand_07::rngs::OsRng; use std::time::Duration; -use topology::NymTopology; const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200); const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200); diff --git a/validator-api/src/network_monitor/gateways_reader.rs b/nym-api/src/network_monitor/gateways_reader.rs similarity index 96% rename from validator-api/src/network_monitor/gateways_reader.rs rename to nym-api/src/network_monitor/gateways_reader.rs index 53c0fd224c..84407272b0 100644 --- a/validator-api/src/network_monitor/gateways_reader.rs +++ b/nym-api/src/network_monitor/gateways_reader.rs @@ -1,8 +1,8 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crypto::asymmetric::identity; use gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver}; +use nym_crypto::asymmetric::identity; use tokio_stream::StreamMap; pub(crate) type GatewayMessages = Vec<Vec<u8>>; diff --git a/validator-api/src/network_monitor/mod.rs b/nym-api/src/network_monitor/mod.rs similarity index 73% rename from validator-api/src/network_monitor/mod.rs rename to nym-api/src/network_monitor/mod.rs index 8b85d5fd5a..25b6494da5 100644 --- a/validator-api/src/network_monitor/mod.rs +++ b/nym-api/src/network_monitor/mod.rs @@ -1,16 +1,7 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use credential_storage::PersistentStorage; -use crypto::asymmetric::{encryption, identity}; -use futures::channel::mpsc; -use gateway_client::bandwidth::BandwidthController; -use std::sync::Arc; -use task::ShutdownNotifier; -use validator_client::nymd::SigningNymdClient; - -use crate::config::Config; -use crate::contract_cache::ValidatorCache; +use crate::network_monitor; use crate::network_monitor::monitor::preparer::PacketPreparer; use crate::network_monitor::monitor::processor::{ ReceivedProcessor, ReceivedProcessorReceiver, ReceivedProcessorSender, @@ -21,8 +12,17 @@ use crate::network_monitor::monitor::receiver::{ use crate::network_monitor::monitor::sender::PacketSender; use crate::network_monitor::monitor::summary_producer::SummaryProducer; use crate::network_monitor::monitor::Monitor; -use crate::nymd_client::Client; -use crate::storage::ValidatorApiStorage; +use crate::nym_contract_cache::cache::NymContractCache; +use crate::storage::NymApiStorage; +use crate::support::config::Config; +use crate::support::nyxd; +use futures::channel::mpsc; +use gateway_client::bandwidth::BandwidthController; +use nym_credential_storage::PersistentStorage; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_task::TaskManager; +use std::sync::Arc; +use validator_client::nyxd::SigningNyxdClient; pub(crate) mod chunker; pub(crate) mod gateways_reader; @@ -32,25 +32,41 @@ pub(crate) mod test_route; pub(crate) const ROUTE_TESTING_TEST_NONCE: u64 = 0; +pub(crate) fn setup<'a>( + config: &'a Config, + nym_contract_cache_state: &NymContractCache, + storage: &NymApiStorage, + _nyxd_client: nyxd::Client, + system_version: &str, +) -> NetworkMonitorBuilder<'a> { + NetworkMonitorBuilder::new( + config, + _nyxd_client, + system_version, + storage.to_owned(), + nym_contract_cache_state.to_owned(), + ) +} + pub(crate) struct NetworkMonitorBuilder<'a> { config: &'a Config, - _nymd_client: Client<SigningNymdClient>, + _nyxd_client: nyxd::Client, system_version: String, - node_status_storage: ValidatorApiStorage, - validator_cache: ValidatorCache, + node_status_storage: NymApiStorage, + validator_cache: NymContractCache, } impl<'a> NetworkMonitorBuilder<'a> { pub(crate) fn new( config: &'a Config, - _nymd_client: Client<SigningNymdClient>, + _nyxd_client: nyxd::Client, system_version: &str, - node_status_storage: ValidatorApiStorage, - validator_cache: ValidatorCache, + node_status_storage: NymApiStorage, + validator_cache: NymContractCache, ) -> Self { NetworkMonitorBuilder { config, - _nymd_client, + _nyxd_client, system_version: system_version.to_string(), node_status_storage, validator_cache, @@ -78,25 +94,16 @@ impl<'a> NetworkMonitorBuilder<'a> { *encryption_keypair.public_key(), ); - #[cfg(feature = "coconut")] let bandwidth_controller = { - let client = self._nymd_client.0.read().await; - let coconut_api_clients = - validator_client::CoconutApiClient::all_coconut_api_clients(&client) - .await - .expect("Could not query api clients"); + let client = self._nyxd_client.0.read().await; BandwidthController::new( - credential_storage::initialise_storage(self.config.get_credentials_database_path()) - .await, - coconut_api_clients, + nym_credential_storage::initialise_storage( + self.config.get_credentials_database_path(), + ) + .await, + client.clone(), ) }; - #[cfg(not(feature = "coconut"))] - let bandwidth_controller = BandwidthController::new( - credential_storage::initialise_storage(self.config.get_credentials_database_path()) - .await, - ) - .expect("Could not create bandwidth controller"); let packet_sender = new_packet_sender( self.config, @@ -142,7 +149,7 @@ impl NetworkMonitorRunnables { // TODO: note, that is not exactly doing what we want, because when // `ReceivedProcessor` is constructed, it already spawns a future // this needs to be refactored! - pub(crate) fn spawn_tasks(self, shutdown: &ShutdownNotifier) { + pub(crate) fn spawn_tasks(self, shutdown: &TaskManager) { let mut packet_receiver = self.packet_receiver; let mut monitor = self.monitor; let shutdown_listener = shutdown.subscribe(); @@ -154,7 +161,7 @@ impl NetworkMonitorRunnables { fn new_packet_preparer( system_version: &str, - validator_cache: ValidatorCache, + validator_cache: NymContractCache, per_node_test_packets: usize, self_public_identity: identity::PublicKey, self_public_encryption: encryption::PublicKey, @@ -173,7 +180,7 @@ fn new_packet_sender( gateways_status_updater: GatewayClientUpdateSender, local_identity: Arc<identity::KeyPair>, max_sending_rate: usize, - bandwidth_controller: BandwidthController<PersistentStorage>, + bandwidth_controller: BandwidthController<SigningNyxdClient, PersistentStorage>, disabled_credentials_mode: bool, ) -> PacketSender { PacketSender::new( @@ -207,3 +214,25 @@ fn new_packet_receiver( ) -> PacketReceiver { PacketReceiver::new(gateways_status_updater, processor_packets_sender) } + +// TODO: 1) does it still have to have separate builder or could we get rid of it now? +// TODO: 2) how do we make it non-async as other 'start' methods? +pub(crate) async fn start( + config: &Config, + nym_contract_cache_state: &NymContractCache, + storage: &NymApiStorage, + nyxd_client: nyxd::Client, + system_version: &str, + shutdown: &TaskManager, +) { + let monitor_builder = network_monitor::setup( + config, + nym_contract_cache_state, + storage, + nyxd_client, + system_version, + ); + info!("Starting network monitor..."); + let runnables = monitor_builder.build().await; + runnables.spawn_tasks(shutdown); +} diff --git a/validator-api/src/network_monitor/monitor/gateway_clients_cache.rs b/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs similarity index 87% rename from validator-api/src/network_monitor/monitor/gateway_clients_cache.rs rename to nym-api/src/network_monitor/monitor/gateway_clients_cache.rs index 6d7f415912..1b1b46a84c 100644 --- a/validator-api/src/network_monitor/monitor/gateway_clients_cache.rs +++ b/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs @@ -1,23 +1,26 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crypto::asymmetric::identity::PUBLIC_KEY_LENGTH; use gateway_client::GatewayClient; +use nym_crypto::asymmetric::identity::PUBLIC_KEY_LENGTH; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::{Mutex, MutexGuard, TryLockError}; +use validator_client::nyxd::SigningNyxdClient; pub(crate) struct GatewayClientHandle(Arc<GatewayClientHandleInner>); struct GatewayClientHandleInner { - client: Mutex<Option<GatewayClient>>, + client: Mutex<Option<GatewayClient<SigningNyxdClient>>>, raw_identity: [u8; PUBLIC_KEY_LENGTH], } -pub(crate) struct UnlockedGatewayClientHandle<'a>(MutexGuard<'a, Option<GatewayClient>>); +pub(crate) struct UnlockedGatewayClientHandle<'a>( + MutexGuard<'a, Option<GatewayClient<SigningNyxdClient>>>, +); impl GatewayClientHandle { - pub(crate) fn new(gateway_client: GatewayClient) -> Self { + pub(crate) fn new(gateway_client: GatewayClient<SigningNyxdClient>) -> Self { GatewayClientHandle(Arc::new(GatewayClientHandleInner { raw_identity: gateway_client.gateway_identity().to_bytes(), client: Mutex::new(Some(gateway_client)), @@ -56,11 +59,11 @@ impl GatewayClientHandle { } impl<'a> UnlockedGatewayClientHandle<'a> { - pub(crate) fn get_mut_unchecked(&mut self) -> &mut GatewayClient { + pub(crate) fn get_mut_unchecked(&mut self) -> &mut GatewayClient<SigningNyxdClient> { self.0.as_mut().unwrap() } - pub(crate) fn inner_mut(&mut self) -> Option<&mut GatewayClient> { + pub(crate) fn inner_mut(&mut self) -> Option<&mut GatewayClient<SigningNyxdClient>> { self.0.as_mut() } diff --git a/validator-api/src/network_monitor/monitor/gateways_pinger.rs b/nym-api/src/network_monitor/monitor/gateways_pinger.rs similarity index 97% rename from validator-api/src/network_monitor/monitor/gateways_pinger.rs rename to nym-api/src/network_monitor/monitor/gateways_pinger.rs index 24cd581bf3..4d0a0e7f0e 100644 --- a/validator-api/src/network_monitor/monitor/gateways_pinger.rs +++ b/nym-api/src/network_monitor/monitor/gateways_pinger.rs @@ -3,11 +3,11 @@ use crate::network_monitor::monitor::gateway_clients_cache::ActiveGatewayClients; use crate::network_monitor::monitor::receiver::{GatewayClientUpdate, GatewayClientUpdateSender}; -use crypto::asymmetric::identity; -use crypto::asymmetric::identity::PUBLIC_KEY_LENGTH; use log::{debug, info, trace, warn}; +use nym_crypto::asymmetric::identity; +use nym_crypto::asymmetric::identity::PUBLIC_KEY_LENGTH; +use nym_task::TaskClient; use std::time::Duration; -use task::ShutdownListener; use tokio::time::{sleep, Instant}; // TODO: should it perhaps be moved to config along other timeout values? @@ -144,7 +144,7 @@ impl GatewayPinger { info!("Pinging all active gateways took {:?}", time_taken); } - pub(crate) async fn run(&self, mut shutdown: ShutdownListener) { + pub(crate) async fn run(&self, mut shutdown: TaskClient) { while !shutdown.is_shutdown() { tokio::select! { _ = sleep(self.pinging_interval) => { diff --git a/validator-api/src/network_monitor/monitor/mod.rs b/nym-api/src/network_monitor/monitor/mod.rs similarity index 94% rename from validator-api/src/network_monitor/monitor/mod.rs rename to nym-api/src/network_monitor/monitor/mod.rs index e2d1a07128..3dc98ccb9d 100644 --- a/validator-api/src/network_monitor/monitor/mod.rs +++ b/nym-api/src/network_monitor/monitor/mod.rs @@ -1,18 +1,18 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crate::config::Config; use crate::network_monitor::monitor::preparer::PacketPreparer; use crate::network_monitor::monitor::processor::ReceivedProcessor; use crate::network_monitor::monitor::sender::PacketSender; use crate::network_monitor::monitor::summary_producer::{SummaryProducer, TestSummary}; use crate::network_monitor::test_packet::TestPacket; use crate::network_monitor::test_route::TestRoute; -use crate::storage::ValidatorApiStorage; +use crate::storage::NymApiStorage; +use crate::support::config::Config; use log::{debug, error, info}; +use nym_task::TaskClient; use std::collections::{HashMap, HashSet}; use std::process; -use task::ShutdownListener; use tokio::time::{sleep, Duration, Instant}; pub(crate) mod gateway_clients_cache; @@ -29,7 +29,7 @@ pub(super) struct Monitor { packet_sender: PacketSender, received_processor: ReceivedProcessor, summary_producer: SummaryProducer, - node_status_storage: ValidatorApiStorage, + node_status_storage: NymApiStorage, run_interval: Duration, gateway_ping_interval: Duration, packet_delivery_timeout: Duration, @@ -52,7 +52,7 @@ impl Monitor { packet_sender: PacketSender, received_processor: ReceivedProcessor, summary_producer: SummaryProducer, - node_status_storage: ValidatorApiStorage, + node_status_storage: NymApiStorage, ) -> Self { Monitor { test_nonce: 1, @@ -88,10 +88,7 @@ impl Monitor { ) .await { - error!( - "Failed to submit monitor run information to the database - {}", - err - ); + error!("Failed to submit monitor run information to the database - {err}",); // TODO: slightly more graceful shutdown here process::exit(1); @@ -124,12 +121,8 @@ impl Monitor { for route in routes { let mut packet_preparer = self.packet_preparer.clone(); let route = route.clone(); - let route_test_packets = self.route_test_packets; - let gateway_packets = tokio::spawn(async move { - packet_preparer.prepare_test_route_viability_packets(&route, route_test_packets) - }) - .await - .unwrap(); + let gateway_packets = packet_preparer + .prepare_test_route_viability_packets(&route, self.route_test_packets); packets.push(gateway_packets); } @@ -301,7 +294,7 @@ impl Monitor { self.test_nonce += 1; } - pub(crate) async fn run(&mut self, mut shutdown: ShutdownListener) { + pub(crate) async fn run(&mut self, mut shutdown: TaskClient) { self.received_processor.start_receiving(); // wait for validator cache to be ready diff --git a/validator-api/src/network_monitor/monitor/preparer.rs b/nym-api/src/network_monitor/monitor/preparer.rs similarity index 95% rename from validator-api/src/network_monitor/monitor/preparer.rs rename to nym-api/src/network_monitor/monitor/preparer.rs index 2351dcca64..a0df027e9a 100644 --- a/validator-api/src/network_monitor/monitor/preparer.rs +++ b/nym-api/src/network_monitor/monitor/preparer.rs @@ -1,23 +1,23 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crate::contract_cache::ValidatorCache; use crate::network_monitor::chunker::Chunker; use crate::network_monitor::monitor::sender::GatewayPackets; use crate::network_monitor::test_packet::{NodeType, TestPacket}; use crate::network_monitor::test_route::TestRoute; -use crypto::asymmetric::{encryption, identity}; +use crate::nym_contract_cache::cache::NymContractCache; use log::info; -use mixnet_contract_common::{Addr, GatewayBond, Layer, MixId, MixNodeBond}; -use nymsphinx::addressing::clients::Recipient; -use nymsphinx::forwarding::packet::MixPacket; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_mixnet_contract_common::{Addr, GatewayBond, Layer, MixId, MixNodeBond}; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::forwarding::packet::MixPacket; +use nym_topology::{gateway, mix, NymTopology}; use rand::seq::SliceRandom; use rand::{thread_rng, Rng}; use std::collections::{HashMap, HashSet}; use std::convert::TryInto; use std::fmt::{self, Display, Formatter}; use std::time::Duration; -use topology::{gateway, mix, NymTopology}; // declared type aliases for easier code reasoning type Version = String; @@ -134,7 +134,7 @@ pub(crate) struct PreparedPackets { pub(crate) struct PacketPreparer { system_version: String, chunker: Option<Chunker>, - validator_cache: ValidatorCache, + validator_cache: NymContractCache, /// Number of test packets sent to each node per_node_test_packets: usize, @@ -150,7 +150,7 @@ pub(crate) struct PacketPreparer { impl PacketPreparer { pub(crate) fn new( system_version: &str, - validator_cache: ValidatorCache, + validator_cache: NymContractCache, per_node_test_packets: usize, self_public_identity: identity::PublicKey, self_public_encryption: encryption::PublicKey, @@ -380,7 +380,6 @@ impl PacketPreparer { GatewayPackets::new( route.gateway_clients_address(), route.gateway_identity(), - route.gateway_owner(), mix_packets, ) } @@ -453,7 +452,6 @@ impl PacketPreparer { let recipient = self.create_packet_sender(test_route.gateway()); let gateway_identity = test_route.gateway_identity(); let gateway_address = test_route.gateway_clients_address(); - let gateway_owner = test_route.gateway_owner(); // it's actually going to be a tiny bit more due to gateway testing, but it's a good enough approximation let mut mix_packets = Vec::with_capacity(mixnodes.len() * self.per_node_test_packets); @@ -471,9 +469,7 @@ impl PacketPreparer { let gateway_packets = all_gateway_packets .entry(gateway_identity.to_bytes()) - .or_insert_with(|| { - GatewayPackets::empty(gateway_address, gateway_identity, gateway_owner) - }); + .or_insert_with(|| GatewayPackets::empty(gateway_address, gateway_identity)); gateway_packets.push_packets(mix_packets); // and for each gateway... @@ -482,7 +478,6 @@ impl PacketPreparer { let test_packet = TestPacket::from_gateway(gateway, test_route.id(), test_nonce); let gateway_identity = gateway.identity_key; let gateway_address = gateway.clients_address(); - let gateway_owner = gateway.owner.clone(); let recipient = self.create_packet_sender(gateway); let topology = test_route.substitute_gateway(gateway); // produce n mix packets @@ -495,9 +490,7 @@ impl PacketPreparer { // or create a new one let gateway_packets = all_gateway_packets .entry(gateway_identity.to_bytes()) - .or_insert_with(|| { - GatewayPackets::empty(gateway_address, gateway_identity, gateway_owner) - }); + .or_insert_with(|| GatewayPackets::empty(gateway_address, gateway_identity)); gateway_packets.push_packets(gateway_mix_packets); } } diff --git a/validator-api/src/network_monitor/monitor/processor.rs b/nym-api/src/network_monitor/monitor/processor.rs similarity index 83% rename from validator-api/src/network_monitor/monitor/processor.rs rename to nym-api/src/network_monitor/monitor/processor.rs index 7b0649a49d..3ae082a143 100644 --- a/validator-api/src/network_monitor/monitor/processor.rs +++ b/nym-api/src/network_monitor/monitor/processor.rs @@ -2,45 +2,39 @@ // SPDX-License-Identifier: Apache-2.0 use crate::network_monitor::gateways_reader::GatewayMessages; -use crate::network_monitor::test_packet::TestPacket; +use crate::network_monitor::test_packet::{TestPacket, TestPacketError}; use crate::network_monitor::ROUTE_TESTING_TEST_NONCE; -use crypto::asymmetric::encryption; use futures::channel::mpsc; use futures::lock::{Mutex, MutexGuard}; use futures::{SinkExt, StreamExt}; use log::warn; -use nymsphinx::receiver::MessageReceiver; -use std::fmt::{self, Display, Formatter}; +use nym_crypto::asymmetric::encryption; +use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError}; use std::mem; use std::sync::Arc; +use thiserror::Error; pub(crate) type ReceivedProcessorSender = mpsc::UnboundedSender<GatewayMessages>; pub(crate) type ReceivedProcessorReceiver = mpsc::UnboundedReceiver<GatewayMessages>; -#[derive(Debug)] +#[derive(Error, Debug)] enum ProcessingError { - MalformedPacketReceived, - NonTestPacketReceived, - NonMatchingNonce(u64), - ReceivedOutsideTestRun, -} + #[error( + "could not recover underlying data from the received packet since it was malformed - {0}" + )] + MalformedPacketReceived(#[from] MessageRecoveryError), -impl Display for ProcessingError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - ProcessingError::MalformedPacketReceived => write!(f, "received malformed packet"), - ProcessingError::NonTestPacketReceived => write!(f, "received a non-test packet"), - ProcessingError::NonMatchingNonce(nonce) => write!( - f, - "received packet with nonce {} which is different than the expected", - nonce - ), - ProcessingError::ReceivedOutsideTestRun => write!( - f, - "received packet while the test is currently not in progress" - ), - } - } + #[error("received a mix packet that was NOT a proper network monitor test packet")] + NonTestPacketReceived, + + #[error("the received test packet was malformed - {0}")] + MalformedTestPacket(#[from] TestPacketError), + + #[error("received packet with an unexpected nonce. Got: {received}, expected: {expected}")] + NonMatchingNonce { received: u64, expected: u64 }, + + #[error("received a mix packet while no test run is currently in progress")] + ReceivedOutsideTestRun, } // we can't use Notify due to possible edge case where both notification are consumed at once @@ -80,23 +74,20 @@ impl ReceivedProcessorInner { .recover_plaintext_from_regular_packet( self.client_encryption_keypair.private_key(), &mut message, - ) - .map_err(|_| ProcessingError::MalformedPacketReceived)?; - let fragment = self - .message_receiver - .recover_fragment(plaintext) - .map_err(|_| ProcessingError::MalformedPacketReceived)?; + )?; + let fragment = self.message_receiver.recover_fragment(plaintext)?; let (recovered, _) = self .message_receiver - .insert_new_fragment(fragment) - .map_err(|_| ProcessingError::MalformedPacketReceived)? + .insert_new_fragment(fragment)? .ok_or(ProcessingError::NonTestPacketReceived)?; // if it's a test packet it MUST BE reconstructed with single fragment - let test_packet = TestPacket::try_from_bytes(&recovered.into_inner_data()) - .map_err(|_| ProcessingError::MalformedPacketReceived)?; + let test_packet = TestPacket::try_from_bytes(&recovered.into_inner_data())?; // we know nonce is NOT none if test_packet.test_nonce() != self.test_nonce.unwrap() { - return Err(ProcessingError::NonMatchingNonce(test_packet.test_nonce())); + return Err(ProcessingError::NonMatchingNonce { + received: test_packet.test_nonce(), + expected: self.test_nonce.unwrap(), + }); } self.received_packets.push(test_packet); @@ -162,7 +153,7 @@ impl ReceivedProcessor { Some(messages) => { for message in messages { if let Err(err) = inner.on_message(message) { - warn!(target: "Monitor", "failed to process received gateway message - {}", err) + warn!(target: "Monitor", "failed to process received gateway message - {err}") } } } diff --git a/validator-api/src/network_monitor/monitor/receiver.rs b/nym-api/src/network_monitor/monitor/receiver.rs similarity index 95% rename from validator-api/src/network_monitor/monitor/receiver.rs rename to nym-api/src/network_monitor/monitor/receiver.rs index 991da5fa05..fd7d441b30 100644 --- a/validator-api/src/network_monitor/monitor/receiver.rs +++ b/nym-api/src/network_monitor/monitor/receiver.rs @@ -3,11 +3,11 @@ use crate::network_monitor::gateways_reader::{GatewayMessages, GatewaysReader}; use crate::network_monitor::monitor::processor::ReceivedProcessorSender; -use crypto::asymmetric::identity; use futures::channel::mpsc; use futures::StreamExt; use gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver}; -use task::ShutdownListener; +use nym_crypto::asymmetric::identity; +use nym_task::TaskClient; pub(crate) type GatewayClientUpdateSender = mpsc::UnboundedSender<GatewayClientUpdate>; pub(crate) type GatewayClientUpdateReceiver = mpsc::UnboundedReceiver<GatewayClientUpdate>; @@ -56,7 +56,7 @@ impl PacketReceiver { .expect("packet processor seems to have crashed!"); } - pub(crate) async fn run(&mut self, mut shutdown: ShutdownListener) { + pub(crate) async fn run(&mut self, mut shutdown: TaskClient) { while !shutdown.is_shutdown() { tokio::select! { biased; diff --git a/validator-api/src/network_monitor/monitor/sender.rs b/nym-api/src/network_monitor/monitor/sender.rs similarity index 94% rename from validator-api/src/network_monitor/monitor/sender.rs rename to nym-api/src/network_monitor/monitor/sender.rs index d2fa815a11..cf536a6322 100644 --- a/validator-api/src/network_monitor/monitor/sender.rs +++ b/nym-api/src/network_monitor/monitor/sender.rs @@ -6,9 +6,6 @@ use crate::network_monitor::monitor::gateway_clients_cache::{ }; use crate::network_monitor::monitor::gateways_pinger::GatewayPinger; use crate::network_monitor::monitor::receiver::{GatewayClientUpdate, GatewayClientUpdateSender}; -use config::defaults::REMAINING_BANDWIDTH_THRESHOLD; -use credential_storage::PersistentStorage; -use crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH}; use futures::channel::mpsc; use futures::stream::{self, FuturesUnordered, StreamExt}; use futures::task::Context; @@ -16,7 +13,11 @@ use futures::{Future, Stream}; use gateway_client::error::GatewayClientError; use gateway_client::{AcknowledgementReceiver, GatewayClient, MixnetMessageReceiver}; use log::{debug, info, trace, warn}; -use nymsphinx::forwarding::packet::MixPacket; +use nym_config::defaults::REMAINING_BANDWIDTH_THRESHOLD; +use nym_credential_storage::PersistentStorage; +use nym_crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH}; +use nym_sphinx::forwarding::packet::MixPacket; +use nym_task::TaskClient; use pin_project::pin_project; use std::mem; use std::num::NonZeroUsize; @@ -24,9 +25,9 @@ use std::pin::Pin; use std::sync::Arc; use std::task::Poll; use std::time::Duration; -use task::ShutdownListener; use gateway_client::bandwidth::BandwidthController; +use validator_client::nyxd::SigningNyxdClient; const TIME_CHUNK_SIZE: Duration = Duration::from_millis(50); @@ -38,9 +39,6 @@ pub(crate) struct GatewayPackets { /// Public key of the target gateway. pub(crate) pub_key: identity::PublicKey, - /// The address of the gateway owner. - pub(crate) gateway_owner: String, - /// All the packets that are going to get sent to the gateway. pub(crate) packets: Vec<MixPacket>, } @@ -49,26 +47,19 @@ impl GatewayPackets { pub(crate) fn new( clients_address: String, pub_key: identity::PublicKey, - gateway_owner: String, packets: Vec<MixPacket>, ) -> Self { GatewayPackets { clients_address, pub_key, - gateway_owner, packets, } } - pub(crate) fn empty( - clients_address: String, - pub_key: identity::PublicKey, - gateway_owner: String, - ) -> Self { + pub(crate) fn empty(clients_address: String, pub_key: identity::PublicKey) -> Self { GatewayPackets { clients_address, pub_key, - gateway_owner, packets: Vec::new(), } } @@ -90,7 +81,7 @@ struct FreshGatewayClientData { gateways_status_updater: GatewayClientUpdateSender, local_identity: Arc<identity::KeyPair>, gateway_response_timeout: Duration, - bandwidth_controller: BandwidthController<PersistentStorage>, + bandwidth_controller: BandwidthController<SigningNyxdClient, PersistentStorage>, disabled_credentials_mode: bool, } @@ -148,7 +139,7 @@ impl PacketSender { gateway_connection_timeout: Duration, max_concurrent_clients: usize, max_sending_rate: usize, - bandwidth_controller: BandwidthController<PersistentStorage>, + bandwidth_controller: BandwidthController<SigningNyxdClient, PersistentStorage>, disabled_credentials_mode: bool, ) -> Self { PacketSender { @@ -166,11 +157,7 @@ impl PacketSender { } } - pub(crate) fn spawn_gateways_pinger( - &self, - pinging_interval: Duration, - shutdown: ShutdownListener, - ) { + pub(crate) fn spawn_gateways_pinger(&self, pinging_interval: Duration, shutdown: TaskClient) { let gateway_pinger = GatewayPinger::new( self.active_gateway_clients.clone(), self.fresh_gateway_client_data @@ -185,7 +172,6 @@ impl PacketSender { fn new_gateway_client_handle( address: String, identity: identity::PublicKey, - owner: String, fresh_gateway_client_data: &FreshGatewayClientData, ) -> ( GatewayClientHandle, @@ -203,13 +189,12 @@ impl PacketSender { address, Arc::clone(&fresh_gateway_client_data.local_identity), identity, - owner, None, message_sender, ack_sender, fresh_gateway_client_data.gateway_response_timeout, Some(fresh_gateway_client_data.bandwidth_controller.clone()), - task::ShutdownListener::dummy(), + nym_task::TaskClient::dummy(), ); gateway_client @@ -222,7 +207,7 @@ impl PacketSender { } async fn attempt_to_send_packets( - client: &mut GatewayClient, + client: &mut GatewayClient<SigningNyxdClient>, mut mix_packets: Vec<MixPacket>, max_sending_rate: usize, ) -> Result<(), GatewayClientError> { @@ -283,7 +268,6 @@ impl PacketSender { async fn create_new_gateway_client_handle_and_authenticate( address: String, identity: identity::PublicKey, - owner: String, fresh_gateway_client_data: &FreshGatewayClientData, gateway_connection_timeout: Duration, ) -> Option<( @@ -291,7 +275,7 @@ impl PacketSender { (MixnetMessageReceiver, AcknowledgementReceiver), )> { let (new_client, (message_receiver, ack_receiver)) = - Self::new_gateway_client_handle(address, identity, owner, fresh_gateway_client_data); + Self::new_gateway_client_handle(address, identity, fresh_gateway_client_data); // Put this in timeout in case the gateway has incorrectly set their ulimit and our connection // gets stuck in their TCP queue and just hangs on our end but does not terminate @@ -331,7 +315,7 @@ impl PacketSender { } async fn check_remaining_bandwidth( - client: &mut GatewayClient, + client: &mut GatewayClient<SigningNyxdClient>, ) -> Result<(), GatewayClientError> { if client.remaining_bandwidth() < REMAINING_BANDWIDTH_THRESHOLD { Err(GatewayClientError::NotEnoughBandwidth( @@ -368,7 +352,6 @@ impl PacketSender { Self::create_new_gateway_client_handle_and_authenticate( packets.clients_address, packets.pub_key, - packets.gateway_owner, &fresh_gateway_client_data, gateway_connection_timeout, ) diff --git a/validator-api/src/network_monitor/monitor/summary_producer.rs b/nym-api/src/network_monitor/monitor/summary_producer.rs similarity index 99% rename from validator-api/src/network_monitor/monitor/summary_producer.rs rename to nym-api/src/network_monitor/monitor/summary_producer.rs index 0262dba4fb..704084c338 100644 --- a/validator-api/src/network_monitor/monitor/summary_producer.rs +++ b/nym-api/src/network_monitor/monitor/summary_producer.rs @@ -4,7 +4,7 @@ use crate::network_monitor::monitor::preparer::{InvalidNode, TestedNode}; use crate::network_monitor::test_packet::{NodeType, TestPacket}; use crate::network_monitor::test_route::TestRoute; -use mixnet_contract_common::MixId; +use nym_mixnet_contract_common::MixId; use std::collections::HashMap; use std::fmt::{Display, Formatter}; diff --git a/validator-api/src/network_monitor/test_packet.rs b/nym-api/src/network_monitor/test_packet.rs similarity index 86% rename from validator-api/src/network_monitor/test_packet.rs rename to nym-api/src/network_monitor/test_packet.rs index 8f5e0bde5e..0a38e642b7 100644 --- a/validator-api/src/network_monitor/test_packet.rs +++ b/nym-api/src/network_monitor/test_packet.rs @@ -2,14 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use crate::network_monitor::monitor::preparer::TestedNode; -use crypto::asymmetric::identity; -use mixnet_contract_common::MixId; +use nym_crypto::asymmetric::identity; +use nym_crypto::asymmetric::identity::Ed25519RecoveryError; +use nym_mixnet_contract_common::MixId; +use nym_topology::{gateway, mix}; use std::convert::TryInto; use std::fmt::{self, Display, Formatter}; use std::hash::{Hash, Hasher}; use std::mem; use std::str::Utf8Error; -use topology::{gateway, mix}; +use thiserror::Error; const MIXNODE_TYPE: u8 = 0; const GATEWAY_TYPE: u8 = 1; @@ -67,24 +69,25 @@ impl NodeType { } } -#[derive(Debug)] +#[derive(Debug, Error)] pub(crate) enum TestPacketError { - IncompletePacket, + #[error( + "the received packet was incomplete. Got {received} bytes but expected at least {min_expected}" + )] + IncompletePacket { + received: usize, + min_expected: usize, + }, + + // TODO: ideally this should contain more information but that'd require more refactoring than I'm willing to commit to now + #[error("the received packet did not contain a valid node type")] InvalidNodeType, - InvalidNodeKey, - InvalidOwner(Utf8Error), -} -impl From<identity::Ed25519RecoveryError> for TestPacketError { - fn from(_: identity::Ed25519RecoveryError) -> Self { - TestPacketError::InvalidNodeKey - } -} + #[error("the received node identity key was malformed - {0}")] + InvalidNodeKey(#[from] Ed25519RecoveryError), -impl From<Utf8Error> for TestPacketError { - fn from(err: Utf8Error) -> Self { - TestPacketError::InvalidOwner(err) - } + #[error("the received packet contained malformed owner data - {0}")] + InvalidOwner(#[from] Utf8Error), } #[derive(Eq, Clone, Debug)] @@ -184,7 +187,10 @@ impl TestPacket { let n = mem::size_of::<u64>(); if b.len() < 2 * n + 1 + identity::PUBLIC_KEY_LENGTH { - return Err(TestPacketError::IncompletePacket); + return Err(TestPacketError::IncompletePacket { + received: b.len(), + min_expected: 2 * n + 1 + identity::PUBLIC_KEY_LENGTH, + }); } // those unwraps can't fail as we've already checked for the size diff --git a/validator-api/src/network_monitor/test_route/mod.rs b/nym-api/src/network_monitor/test_route/mod.rs similarity index 95% rename from validator-api/src/network_monitor/test_route/mod.rs rename to nym-api/src/network_monitor/test_route/mod.rs index ddc420e749..c4b98ac5a0 100644 --- a/validator-api/src/network_monitor/test_route/mod.rs +++ b/nym-api/src/network_monitor/test_route/mod.rs @@ -3,9 +3,9 @@ use crate::network_monitor::test_packet::{NodeType, TestPacket}; use crate::network_monitor::ROUTE_TESTING_TEST_NONCE; -use crypto::asymmetric::identity; +use nym_crypto::asymmetric::identity; +use nym_topology::{gateway, mix, NymTopology}; use std::fmt::{Debug, Formatter}; -use topology::{gateway, mix, NymTopology}; #[derive(Clone)] pub(crate) struct TestRoute { @@ -66,10 +66,6 @@ impl TestRoute { self.gateway().identity_key } - pub(crate) fn gateway_owner(&self) -> String { - self.gateway().owner.clone() - } - pub(crate) fn topology(&self) -> &NymTopology { &self.nodes } diff --git a/nym-api/src/node_status_api/cache/data.rs b/nym-api/src/node_status_api/cache/data.rs new file mode 100644 index 0000000000..b1fb52ea6e --- /dev/null +++ b/nym-api/src/node_status_api/cache/data.rs @@ -0,0 +1,23 @@ +use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated}; + +use crate::support::caching::Cache; + +use super::inclusion_probabilities::InclusionProbabilities; + +#[derive(Default)] +pub(crate) struct NodeStatusCacheData { + pub(crate) mixnodes_annotated: Cache<Vec<MixNodeBondAnnotated>>, + pub(crate) rewarded_set_annotated: Cache<Vec<MixNodeBondAnnotated>>, + pub(crate) active_set_annotated: Cache<Vec<MixNodeBondAnnotated>>, + + pub(crate) gateways_annotated: Cache<Vec<GatewayBondAnnotated>>, + + // Estimated active set inclusion probabilities from Monte Carlo simulation + pub(crate) inclusion_probabilities: Cache<InclusionProbabilities>, +} + +impl NodeStatusCacheData { + pub fn new() -> Self { + Self::default() + } +} diff --git a/validator-api/src/node_status_api/cache/inclusion_probabilities.rs b/nym-api/src/node_status_api/cache/inclusion_probabilities.rs similarity index 89% rename from validator-api/src/node_status_api/cache/inclusion_probabilities.rs rename to nym-api/src/node_status_api/cache/inclusion_probabilities.rs index 21c1bdcab5..7a59de4145 100644 --- a/validator-api/src/node_status_api/cache/inclusion_probabilities.rs +++ b/nym-api/src/node_status_api/cache/inclusion_probabilities.rs @@ -1,9 +1,9 @@ -use contracts_common::truncate_decimal; -use mixnet_contract_common::{MixId, MixNodeDetails, RewardingParams}; +use nym_api_requests::models::InclusionProbability; +use nym_contracts_common::truncate_decimal; +use nym_mixnet_contract_common::{MixId, MixNodeDetails, RewardingParams}; use serde::Serialize; use std::time::Duration; use tap::TapFallible; -use validator_api_requests::models::InclusionProbability; const MAX_SIMULATION_SAMPLES: u64 = 5000; const MAX_SIMULATION_TIME_SEC: u64 = 15; @@ -46,7 +46,7 @@ fn compute_inclusion_probabilities( // Compute inclusion probabilitites and keep track of how long time it took. let mut rng = rand::thread_rng(); - let results = inclusion_probability::simulate_selection_probability_mixnodes( + let results = nym_inclusion_probability::simulate_selection_probability_mixnodes( &mixnode_total_bonds, active_set_size as usize, standby_set_size as usize, @@ -75,7 +75,7 @@ fn unzip_into_mixnode_ids_and_total_bonds(mixnodes: &[MixNodeDetails]) -> (Vec<M fn zip_ids_together_with_results( ids: &[MixId], - results: &inclusion_probability::SelectionProbability, + results: &nym_inclusion_probability::SelectionProbability, ) -> Vec<InclusionProbability> { ids.iter() .zip(results.active_set_probability.iter()) diff --git a/nym-api/src/node_status_api/cache/mod.rs b/nym-api/src/node_status_api/cache/mod.rs new file mode 100644 index 0000000000..ba74ab561c --- /dev/null +++ b/nym-api/src/node_status_api/cache/mod.rs @@ -0,0 +1,146 @@ +// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::support::caching::Cache; + +use self::data::NodeStatusCacheData; +use self::inclusion_probabilities::InclusionProbabilities; +use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, MixnodeStatus}; +use nym_mixnet_contract_common::MixId; +use rocket::fairing::AdHoc; +use std::{sync::Arc, time::Duration}; +use thiserror::Error; +use tokio::sync::RwLockReadGuard; +use tokio::{sync::RwLock, time}; + +const CACHE_TIMEOUT_MS: u64 = 100; + +pub mod data; +mod inclusion_probabilities; +mod node_sets; +pub mod refresher; + +#[derive(Debug, Error)] +enum NodeStatusCacheError { + #[error("failed to simulate selection probabilities for mixnodes, not updating cache")] + SimulationFailed, + + #[error("the current interval information is not available at the moment")] + SourceDataMissing, +} + +/// A node status cache suitable for caching values computed in one sweep, such as active set +/// inclusion probabilities that are computed for all mixnodes at the same time. +/// +/// The cache can be triggered to update on contract cache changes, and/or periodically on a timer. +#[derive(Clone)] +pub struct NodeStatusCache { + inner: Arc<RwLock<NodeStatusCacheData>>, +} + +impl NodeStatusCache { + /// Creates a new cache with no data. + fn new() -> NodeStatusCache { + NodeStatusCache { + inner: Arc::new(RwLock::new(NodeStatusCacheData::new())), + } + } + + pub fn stage() -> AdHoc { + AdHoc::on_ignite("Node Status Cache", |rocket| async { + rocket.manage(Self::new()) + }) + } + + /// Updates the cache with the latest data. + async fn update( + &self, + mixnodes: Vec<MixNodeBondAnnotated>, + rewarded_set: Vec<MixNodeBondAnnotated>, + active_set: Vec<MixNodeBondAnnotated>, + gateways: Vec<GatewayBondAnnotated>, + inclusion_probabilities: InclusionProbabilities, + ) { + match time::timeout(Duration::from_millis(CACHE_TIMEOUT_MS), self.inner.write()).await { + Ok(mut cache) => { + cache.mixnodes_annotated.update(mixnodes); + cache.rewarded_set_annotated.update(rewarded_set); + cache.active_set_annotated.update(active_set); + cache.gateways_annotated.update(gateways); + cache + .inclusion_probabilities + .update(inclusion_probabilities); + } + Err(e) => error!("{e}"), + } + } + + /// Returns a copy of the current cache data. + async fn get<T>( + &self, + fn_arg: impl FnOnce(RwLockReadGuard<'_, NodeStatusCacheData>) -> Cache<T>, + ) -> Option<Cache<T>> { + match time::timeout(Duration::from_millis(CACHE_TIMEOUT_MS), self.inner.read()).await { + Ok(cache) => Some(fn_arg(cache)), + Err(e) => { + error!("{e}"); + None + } + } + } + + pub(crate) async fn mixnodes_annotated_full(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> { + self.get(|c| c.mixnodes_annotated.clone()).await + } + + pub(crate) async fn mixnodes_annotated_filtered(&self) -> Option<Vec<MixNodeBondAnnotated>> { + let full = self.mixnodes_annotated_full().await?; + Some(full.value.into_iter().filter(|m| !m.blacklisted).collect()) + } + + pub(crate) async fn rewarded_set_annotated(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> { + self.get(|c| c.rewarded_set_annotated.clone()).await + } + + pub(crate) async fn active_set_annotated(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> { + self.get(|c| c.active_set_annotated.clone()).await + } + + pub(crate) async fn gateways_annotated_full(&self) -> Option<Cache<Vec<GatewayBondAnnotated>>> { + self.get(|c| c.gateways_annotated.clone()).await + } + + pub(crate) async fn gateways_annotated_filtered(&self) -> Option<Vec<GatewayBondAnnotated>> { + let full = self.gateways_annotated_full().await?; + Some(full.value.into_iter().filter(|m| !m.blacklisted).collect()) + } + + pub(crate) async fn inclusion_probabilities(&self) -> Option<Cache<InclusionProbabilities>> { + self.get(|c| c.inclusion_probabilities.clone()).await + } + + pub async fn mixnode_details( + &self, + mix_id: MixId, + ) -> (Option<MixNodeBondAnnotated>, MixnodeStatus) { + // it might not be the most optimal to possibly iterate the entire vector to find (or not) + // the relevant value. However, the vectors are relatively small (< 10_000 elements, < 1000 for active set) + + let active_set = &self.active_set_annotated().await.unwrap().into_inner(); + if let Some(bond) = active_set.iter().find(|mix| mix.mix_id() == mix_id) { + return (Some(bond.clone()), MixnodeStatus::Active); + } + + let rewarded_set = &self.rewarded_set_annotated().await.unwrap().into_inner(); + if let Some(bond) = rewarded_set.iter().find(|mix| mix.mix_id() == mix_id) { + return (Some(bond.clone()), MixnodeStatus::Standby); + } + + let all_bonded = &self.mixnodes_annotated_filtered().await.unwrap(); + if let Some(bond) = all_bonded.iter().find(|mix| mix.mix_id() == mix_id) { + (Some(bond.clone()), MixnodeStatus::Inactive) + } else { + (None, MixnodeStatus::NotFound) + } + } +} diff --git a/nym-api/src/node_status_api/cache/node_sets.rs b/nym-api/src/node_status_api/cache/node_sets.rs new file mode 100644 index 0000000000..a80c84d2d1 --- /dev/null +++ b/nym-api/src/node_status_api/cache/node_sets.rs @@ -0,0 +1,188 @@ +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}; +use nym_mixnet_contract_common::families::FamilyHead; +use nym_mixnet_contract_common::{reward_params::Performance, Interval, MixId}; +use nym_mixnet_contract_common::{ + GatewayBond, IdentityKey, MixNodeDetails, RewardedSetNodeStatus, RewardingParams, +}; +use std::collections::{HashMap, HashSet}; + +pub(super) fn to_rewarded_set_node_status( + rewarded_set: &[MixNodeDetails], + active_set: &[MixNodeDetails], +) -> HashMap<MixId, RewardedSetNodeStatus> { + let mut rewarded_set_node_status: HashMap<MixId, RewardedSetNodeStatus> = rewarded_set + .iter() + .map(|m| (m.mix_id(), RewardedSetNodeStatus::Standby)) + .collect(); + for mixnode in active_set { + *rewarded_set_node_status + .get_mut(&mixnode.mix_id()) + .expect("All active nodes are rewarded nodes") = RewardedSetNodeStatus::Active; + } + rewarded_set_node_status +} + +pub(super) fn split_into_active_and_rewarded_set( + mixnodes_annotated: &[MixNodeBondAnnotated], + rewarded_set_node_status: &HashMap<u32, RewardedSetNodeStatus>, +) -> (Vec<MixNodeBondAnnotated>, Vec<MixNodeBondAnnotated>) { + let rewarded_set: Vec<_> = mixnodes_annotated + .iter() + .filter(|mixnode| rewarded_set_node_status.get(&mixnode.mix_id()).is_some()) + .cloned() + .collect(); + let active_set: Vec<_> = rewarded_set + .iter() + .filter(|mixnode| { + rewarded_set_node_status + .get(&mixnode.mix_id()) + .map_or(false, RewardedSetNodeStatus::is_active) + }) + .cloned() + .collect(); + (rewarded_set, active_set) +} + +pub(super) async fn get_mixnode_performance_from_storage( + storage: &Option<NymApiStorage>, + mix_id: MixId, + epoch: Interval, +) -> Option<Performance> { + storage + .as_ref()? + .get_average_mixnode_uptime_in_the_last_24hrs( + mix_id, + epoch.current_epoch_end_unix_timestamp(), + ) + .await + .ok() + .map(Into::into) +} + +pub(super) async fn get_gateway_performance_from_storage( + storage: &Option<NymApiStorage>, + gateway_id: &str, + epoch: Interval, +) -> Option<Performance> { + storage + .as_ref()? + .get_average_gateway_uptime_in_the_last_24hrs( + gateway_id, + epoch.current_epoch_end_unix_timestamp(), + ) + .await + .ok() + .map(Into::into) +} + +pub(super) async fn annotate_nodes_with_details( + storage: &Option<NymApiStorage>, + mixnodes: Vec<MixNodeDetails>, + interval_reward_params: RewardingParams, + current_interval: Interval, + rewarded_set: &HashMap<MixId, RewardedSetNodeStatus>, + mix_to_family: Vec<(IdentityKey, FamilyHead)>, + blacklist: &HashSet<MixId>, +) -> Vec<MixNodeBondAnnotated> { + let mix_to_family = mix_to_family + .into_iter() + .collect::<HashMap<IdentityKey, FamilyHead>>(); + + let mut annotated = Vec::new(); + for mixnode in mixnodes { + let stake_saturation = mixnode + .rewarding_details + .bond_saturation(&interval_reward_params); + + let uncapped_stake_saturation = mixnode + .rewarding_details + .uncapped_bond_saturation(&interval_reward_params); + + let rewarded_set_status = rewarded_set.get(&mixnode.mix_id()).copied(); + + // If the performance can't be obtained, because the nym-api was not started with + // the monitoring (and hence, storage), then reward estimates will be all zero + let performance = + get_mixnode_performance_from_storage(storage, mixnode.mix_id(), current_interval) + .await + .unwrap_or_default(); + + let reward_estimate = compute_reward_estimate( + &mixnode, + performance, + rewarded_set_status, + interval_reward_params, + current_interval, + ); + + let node_performance = if let Some(storage) = storage { + storage + .construct_mixnode_report(mixnode.mix_id()) + .await + .map(NodePerformance::from) + .ok() + } else { + None + } + .unwrap_or_default(); + + let (estimated_operator_apy, estimated_delegators_apy) = + compute_apy_from_reward(&mixnode, reward_estimate, current_interval); + + let family = mix_to_family + .get(&mixnode.bond_information.identity().to_string()) + .cloned(); + + annotated.push(MixNodeBondAnnotated { + blacklisted: blacklist.contains(&mixnode.mix_id()), + mixnode_details: mixnode, + stake_saturation, + uncapped_stake_saturation, + performance, + node_performance, + estimated_operator_apy, + estimated_delegators_apy, + family, + }); + } + annotated +} + +pub(crate) async fn annotate_gateways_with_details( + storage: &Option<NymApiStorage>, + gateway_bonds: Vec<GatewayBond>, + current_interval: Interval, + blacklist: &HashSet<IdentityKey>, +) -> Vec<GatewayBondAnnotated> { + let mut annotated = Vec::new(); + for gateway_bond in gateway_bonds { + let performance = get_gateway_performance_from_storage( + storage, + gateway_bond.identity(), + current_interval, + ) + .await + .unwrap_or_default(); + + let node_performance = if let Some(storage) = storage { + storage + .construct_gateway_report(gateway_bond.identity()) + .await + .map(NodePerformance::from) + .ok() + } else { + None + } + .unwrap_or_default(); + + annotated.push(GatewayBondAnnotated { + blacklisted: blacklist.contains(&gateway_bond.gateway.identity_key), + gateway_bond, + performance, + node_performance, + }); + } + annotated +} diff --git a/nym-api/src/node_status_api/cache/refresher.rs b/nym-api/src/node_status_api/cache/refresher.rs new file mode 100644 index 0000000000..7e84ea2ee8 --- /dev/null +++ b/nym-api/src/node_status_api/cache/refresher.rs @@ -0,0 +1,175 @@ +use super::NodeStatusCache; +use crate::{ + node_status_api::cache::{ + inclusion_probabilities::InclusionProbabilities, + node_sets::{ + annotate_gateways_with_details, annotate_nodes_with_details, + split_into_active_and_rewarded_set, to_rewarded_set_node_status, + }, + NodeStatusCacheError, + }, + nym_contract_cache::cache::NymContractCache, + storage::NymApiStorage, + support::caching::CacheNotification, +}; +use nym_task::TaskClient; +use std::time::Duration; +use tokio::sync::watch; +use tokio::time; + +// Long running task responsible for keeping the node status cache up-to-date. +pub struct NodeStatusCacheRefresher { + // Main stored data + cache: NodeStatusCache, + fallback_caching_interval: Duration, + + // Sources for when refreshing data + contract_cache: NymContractCache, + contract_cache_listener: watch::Receiver<CacheNotification>, + storage: Option<NymApiStorage>, +} + +impl NodeStatusCacheRefresher { + pub(crate) fn new( + cache: NodeStatusCache, + fallback_caching_interval: Duration, + contract_cache: NymContractCache, + contract_cache_listener: watch::Receiver<CacheNotification>, + storage: Option<NymApiStorage>, + ) -> Self { + Self { + cache, + fallback_caching_interval, + contract_cache, + contract_cache_listener, + storage, + } + } + + /// Runs the node status cache refresher task. + pub async fn run(&mut self, mut shutdown: TaskClient) { + let mut fallback_interval = time::interval(self.fallback_caching_interval); + while !shutdown.is_shutdown() { + tokio::select! { + biased; + _ = shutdown.recv() => { + log::trace!("NodeStatusCacheRefresher: Received shutdown"); + } + // Update node status cache when the contract cache / validator cache is updated + Ok(_) = self.contract_cache_listener.changed() => { + tokio::select! { + _ = self.update_on_notify(&mut fallback_interval) => (), + _ = shutdown.recv() => { + log::trace!("NodeStatusCacheRefresher: Received shutdown"); + } + } + } + // ... however, if we don't receive any notifications we fall back to periodic + // refreshes + _ = fallback_interval.tick() => { + tokio::select! { + _ = self.update_on_timer() => (), + _ = shutdown.recv() => { + log::trace!("NodeStatusCacheRefresher: Received shutdown"); + } + } + } + } + } + log::info!("NodeStatusCacheRefresher: Exiting"); + } + + /// Updates the node status cache when the contract cache / validator cache is updated + async fn update_on_notify(&self, fallback_interval: &mut time::Interval) { + log::debug!( + "Validator cache event detected: {:?}", + &*self.contract_cache_listener.borrow(), + ); + let _ = self.refresh().await; + fallback_interval.reset(); + } + + /// Updates the node status cache when the fallback interval is reached + async fn update_on_timer(&self) { + log::debug!("Timed trigger for the node status cache"); + let have_contract_cache_data = + *self.contract_cache_listener.borrow() != CacheNotification::Start; + + if have_contract_cache_data { + let _ = self.refresh().await; + } else { + log::trace!( + "Skipping updating node status cache, is the contract cache not yet available?" + ); + } + } + + /// Refreshes the node status cache by fetching the latest data from the contract cache + async fn refresh(&self) -> Result<(), NodeStatusCacheError> { + log::info!("Updating node status cache"); + + // Fetch contract cache data to work with + let mixnode_details = self.contract_cache.mixnodes_all().await; + let interval_reward_params = self.contract_cache.interval_reward_params().await; + let current_interval = self.contract_cache.current_interval().await; + let rewarded_set = self.contract_cache.rewarded_set().await; + let active_set = self.contract_cache.active_set().await; + let mix_to_family = self.contract_cache.mix_to_family().await; + let gateway_bonds = self.contract_cache.gateways_all().await; + + // get blacklists + let mixnodes_blacklist = self.contract_cache.mixnodes_blacklist().await; + let gateways_blacklist = self.contract_cache.gateways_blacklist().await; + + let interval_reward_params = + interval_reward_params.ok_or(NodeStatusCacheError::SourceDataMissing)?; + let current_interval = current_interval.ok_or(NodeStatusCacheError::SourceDataMissing)?; + + // Compute inclusion probabilities + let inclusion_probabilities = InclusionProbabilities::compute( + &mixnode_details, + interval_reward_params, + ) + .ok_or_else(|| { + error!("Failed to simulate selection probabilities for mixnodes, not updating cache"); + NodeStatusCacheError::SimulationFailed + })?; + + // Create annotated data + let rewarded_set_node_status = to_rewarded_set_node_status(&rewarded_set, &active_set); + let mixnodes_annotated = annotate_nodes_with_details( + &self.storage, + mixnode_details, + interval_reward_params, + current_interval, + &rewarded_set_node_status, + mix_to_family.to_vec(), + &mixnodes_blacklist, + ) + .await; + + // Create the annotated rewarded and active sets + let (rewarded_set, active_set) = + split_into_active_and_rewarded_set(&mixnodes_annotated, &rewarded_set_node_status); + + let gateways_annotated = annotate_gateways_with_details( + &self.storage, + gateway_bonds, + current_interval, + &gateways_blacklist, + ) + .await; + + // Update the cache + self.cache + .update( + mixnodes_annotated, + rewarded_set, + active_set, + gateways_annotated, + inclusion_probabilities, + ) + .await; + Ok(()) + } +} diff --git a/validator-api/src/node_status_api/helpers.rs b/nym-api/src/node_status_api/helpers.rs similarity index 64% rename from validator-api/src/node_status_api/helpers.rs rename to nym-api/src/node_status_api/helpers.rs index 4b084533bc..6a67910810 100644 --- a/validator-api/src/node_status_api/helpers.rs +++ b/nym-api/src/node_status_api/helpers.rs @@ -1,37 +1,127 @@ // Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crate::contract_cache::Cache; use crate::node_status_api::models::ErrorResponse; -use crate::storage::ValidatorApiStorage; -use crate::{NodeStatusCache, ValidatorCache}; +use crate::storage::NymApiStorage; +use crate::support::caching::Cache; +use crate::{NodeStatusCache, NymContractCache}; use cosmwasm_std::Decimal; -use mixnet_contract_common::reward_params::Performance; -use mixnet_contract_common::{Interval, MixId, RewardedSetNodeStatus}; +use nym_api_requests::models::{ + AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayBondAnnotated, + GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse, + GatewayUptimeResponse, InclusionProbabilityResponse, MixNodeBondAnnotated, + MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse, + MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse, + UptimeResponse, +}; +use nym_mixnet_contract_common::{MixId, RewardedSetNodeStatus}; use rocket::http::Status; use rocket::State; -use validator_api_requests::models::{ - AllInclusionProbabilitiesResponse, ComputeRewardEstParam, InclusionProbabilityResponse, - MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse, - MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse, - StakeSaturationResponse, UptimeResponse, -}; use super::reward_estimate::compute_reward_estimate; -pub(crate) async fn _mixnode_report( - storage: &ValidatorApiStorage, - mix_id: MixId, -) -> Result<MixnodeStatusReportResponse, ErrorResponse> { - storage - .construct_mixnode_report(mix_id) +async fn get_gateway_bond_annotated( + cache: &NodeStatusCache, + identity: &str, +) -> Result<GatewayBondAnnotated, ErrorResponse> { + let gateways = cache + .gateways_annotated_filtered() .await - .map(MixnodeStatusReportResponse::from) + .ok_or(ErrorResponse::new( + "no data available", + Status::ServiceUnavailable, + ))?; + + gateways + .into_iter() + .find(|gateway| gateway.identity() == identity) + .ok_or(ErrorResponse::new( + "gateway bond not found", + Status::NotFound, + )) +} + +async fn get_mixnode_bond_annotated( + cache: &NodeStatusCache, + mix_id: MixId, +) -> Result<MixNodeBondAnnotated, ErrorResponse> { + let mixnodes = cache + .mixnodes_annotated_filtered() + .await + .ok_or(ErrorResponse::new( + "no data available", + Status::ServiceUnavailable, + ))?; + + mixnodes + .into_iter() + .find(|mixnode| mixnode.mix_id() == mix_id) + .ok_or(ErrorResponse::new( + "mixnode bond not found", + Status::NotFound, + )) +} + +pub(crate) async fn _gateway_report( + cache: &NodeStatusCache, + identity: &str, +) -> Result<GatewayStatusReportResponse, ErrorResponse> { + let gateway = get_gateway_bond_annotated(cache, identity).await?; + + Ok(GatewayStatusReportResponse { + identity: gateway.identity().to_owned(), + owner: gateway.owner().to_string(), + most_recent: gateway.node_performance.most_recent.round_to_integer(), + last_hour: gateway.node_performance.last_hour.round_to_integer(), + last_day: gateway.node_performance.last_24h.round_to_integer(), + }) +} + +pub(crate) async fn _gateway_uptime_history( + storage: &NymApiStorage, + identity: &str, +) -> Result<GatewayUptimeHistoryResponse, ErrorResponse> { + storage + .get_gateway_uptime_history(identity) + .await + .map(GatewayUptimeHistoryResponse::from) .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) } +pub(crate) async fn _gateway_core_status_count( + storage: &State<NymApiStorage>, + identity: &str, + since: Option<i64>, +) -> Result<GatewayCoreStatusResponse, ErrorResponse> { + let count = storage + .get_core_gateway_status_count(identity, since) + .await + .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))?; + + Ok(GatewayCoreStatusResponse { + identity: identity.to_string(), + count, + }) +} + +pub(crate) async fn _mixnode_report( + cache: &NodeStatusCache, + mix_id: MixId, +) -> Result<MixnodeStatusReportResponse, ErrorResponse> { + let mixnode = get_mixnode_bond_annotated(cache, mix_id).await?; + + Ok(MixnodeStatusReportResponse { + mix_id, + identity: mixnode.identity_key().to_owned(), + owner: mixnode.owner().to_string(), + most_recent: mixnode.node_performance.most_recent.round_to_integer(), + last_hour: mixnode.node_performance.last_hour.round_to_integer(), + last_day: mixnode.node_performance.last_24h.round_to_integer(), + }) +} + pub(crate) async fn _mixnode_uptime_history( - storage: &ValidatorApiStorage, + storage: &NymApiStorage, mix_id: MixId, ) -> Result<MixnodeUptimeHistoryResponse, ErrorResponse> { storage @@ -42,7 +132,7 @@ pub(crate) async fn _mixnode_uptime_history( } pub(crate) async fn _mixnode_core_status_count( - storage: &State<ValidatorApiStorage>, + storage: &State<NymApiStorage>, mix_id: MixId, since: Option<i64>, ) -> Result<MixnodeCoreStatusResponse, ErrorResponse> { @@ -55,7 +145,7 @@ pub(crate) async fn _mixnode_core_status_count( } pub(crate) async fn _get_mixnode_status( - cache: &ValidatorCache, + cache: &NymContractCache, mix_id: MixId, ) -> MixnodeStatusResponse { MixnodeStatusResponse { @@ -65,7 +155,7 @@ pub(crate) async fn _get_mixnode_status( pub(crate) async fn _get_mixnode_reward_estimation( cache: &State<NodeStatusCache>, - validator_cache: &State<ValidatorCache>, + validator_cache: &State<NymContractCache>, mix_id: MixId, ) -> Result<RewardEstimationResponse, ErrorResponse> { let (mixnode, status) = cache.mixnode_details(mix_id).await; @@ -103,25 +193,10 @@ pub(crate) async fn _get_mixnode_reward_estimation( } } -async fn average_mixnode_performance( - mix_id: MixId, - current_interval: Interval, - storage: &ValidatorApiStorage, -) -> Result<Performance, ErrorResponse> { - storage - .get_average_mixnode_uptime_in_the_last_24hrs( - mix_id, - current_interval.current_epoch_end_unix_timestamp(), - ) - .await - .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) - .map(Into::into) -} - pub(crate) async fn _compute_mixnode_reward_estimation( user_reward_param: ComputeRewardEstParam, cache: &NodeStatusCache, - validator_cache: &ValidatorCache, + validator_cache: &NymContractCache, mix_id: MixId, ) -> Result<RewardEstimationResponse, ErrorResponse> { let (mixnode, actual_status) = cache.mixnode_details(mix_id).await; @@ -205,7 +280,7 @@ pub(crate) async fn _compute_mixnode_reward_estimation( pub(crate) async fn _get_mixnode_stake_saturation( cache: &NodeStatusCache, - validator_cache: &ValidatorCache, + validator_cache: &NymContractCache, mix_id: MixId, ) -> Result<StakeSaturationResponse, ErrorResponse> { let (mixnode, _) = cache.mixnode_details(mix_id).await; @@ -254,21 +329,28 @@ pub(crate) async fn _get_mixnode_inclusion_probability( } pub(crate) async fn _get_mixnode_avg_uptime( - cache: &ValidatorCache, - storage: &ValidatorApiStorage, + cache: &NodeStatusCache, mix_id: MixId, ) -> Result<UptimeResponse, ErrorResponse> { - let current_interval = cache - .current_interval() - .await - .into_inner() - .ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?; - - let performance = average_mixnode_performance(mix_id, current_interval, storage).await?; + let mixnode = get_mixnode_bond_annotated(cache, mix_id).await?; Ok(UptimeResponse { mix_id, - avg_uptime: performance.round_to_integer(), + avg_uptime: mixnode.node_performance.last_24h.round_to_integer(), + node_performance: mixnode.node_performance, + }) +} + +pub(crate) async fn _get_gateway_avg_uptime( + cache: &NodeStatusCache, + identity: &str, +) -> Result<GatewayUptimeResponse, ErrorResponse> { + let gateway = get_gateway_bond_annotated(cache, identity).await?; + + Ok(GatewayUptimeResponse { + identity: identity.to_string(), + avg_uptime: gateway.node_performance.last_24h.round_to_integer(), + node_performance: gateway.node_performance, }) } @@ -288,7 +370,7 @@ pub(crate) async fn _get_mixnode_inclusion_probabilities( }) } else { Err(ErrorResponse::new( - "No data available".to_string(), + "No data available", Status::ServiceUnavailable, )) } @@ -296,7 +378,16 @@ pub(crate) async fn _get_mixnode_inclusion_probabilities( pub(crate) async fn _get_mixnodes_detailed(cache: &NodeStatusCache) -> Vec<MixNodeBondAnnotated> { cache - .mixnodes_annotated() + .mixnodes_annotated_filtered() + .await + .unwrap_or_default() +} + +pub(crate) async fn _get_mixnodes_detailed_unfiltered( + cache: &NodeStatusCache, +) -> Vec<MixNodeBondAnnotated> { + cache + .mixnodes_annotated_full() .await .unwrap_or_default() .into_inner() @@ -319,3 +410,20 @@ pub(crate) async fn _get_active_set_detailed(cache: &NodeStatusCache) -> Vec<Mix .unwrap_or_default() .into_inner() } + +pub(crate) async fn _get_gateways_detailed(cache: &NodeStatusCache) -> Vec<GatewayBondAnnotated> { + cache + .gateways_annotated_filtered() + .await + .unwrap_or_default() +} + +pub(crate) async fn _get_gateways_detailed_unfiltered( + cache: &NodeStatusCache, +) -> Vec<GatewayBondAnnotated> { + cache + .gateways_annotated_full() + .await + .unwrap_or_default() + .into_inner() +} diff --git a/validator-api/src/node_status_api/local_guard.rs b/nym-api/src/node_status_api/local_guard.rs similarity index 100% rename from validator-api/src/node_status_api/local_guard.rs rename to nym-api/src/node_status_api/local_guard.rs diff --git a/validator-api/src/node_status_api/mod.rs b/nym-api/src/node_status_api/mod.rs similarity index 61% rename from validator-api/src/node_status_api/mod.rs rename to nym-api/src/node_status_api/mod.rs index cbcf1784f2..6906e8aab3 100644 --- a/validator-api/src/node_status_api/mod.rs +++ b/nym-api/src/node_status_api/mod.rs @@ -1,11 +1,19 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -pub(crate) use cache::{NodeStatusCache, NodeStatusCacheRefresher}; +pub(crate) use cache::NodeStatusCache; +use nym_task::TaskManager; use okapi::openapi3::OpenApi; use rocket::Route; use rocket_okapi::{openapi_get_routes_spec, settings::OpenApiSettings}; use std::time::Duration; + +use crate::{ + nym_contract_cache::cache::NymContractCache, + support::{self, config::Config, storage}, +}; + +use self::cache::refresher::NodeStatusCacheRefresher; pub(crate) mod cache; pub(crate) mod helpers; pub(crate) mod local_guard; @@ -37,10 +45,14 @@ pub(crate) fn node_status_routes( routes::get_mixnode_stake_saturation, routes::get_mixnode_inclusion_probability, routes::get_mixnode_avg_uptime, + routes::get_gateway_avg_uptime, routes::get_mixnode_inclusion_probabilities, routes::get_mixnodes_detailed, + routes::get_mixnodes_detailed_unfiltered, routes::get_rewarded_set_detailed, routes::get_active_set_detailed, + routes::get_gateways_detailed, + routes::get_gateways_detailed_unfiltered, ] } else { // in the minimal variant we would not have access to endpoints relying on existence @@ -56,3 +68,26 @@ pub(crate) fn node_status_routes( ] } } + +/// Spawn the node status cache refresher. +/// +/// It is primarily refreshed in-sync with the nym contract cache, however provide a fallback +/// caching interval that is twice the nym contract cache +pub(crate) fn start_cache_refresh( + config: &Config, + nym_contract_cache_state: &NymContractCache, + node_status_cache_state: &NodeStatusCache, + storage: Option<&storage::NymApiStorage>, + nym_contract_cache_listener: tokio::sync::watch::Receiver<support::caching::CacheNotification>, + shutdown: &TaskManager, +) { + let mut nym_api_cache_refresher = NodeStatusCacheRefresher::new( + node_status_cache_state.to_owned(), + config.get_node_status_caching_interval(), + nym_contract_cache_state.to_owned(), + nym_contract_cache_listener, + storage.cloned(), + ); + let shutdown_listener = shutdown.subscribe(); + tokio::spawn(async move { nym_api_cache_refresher.run(shutdown_listener).await }); +} diff --git a/validator-api/src/node_status_api/models.rs b/nym-api/src/node_status_api/models.rs similarity index 80% rename from validator-api/src/node_status_api/models.rs rename to nym-api/src/node_status_api/models.rs index ae9661bf23..b1cb8cd79b 100644 --- a/validator-api/src/node_status_api/models.rs +++ b/nym-api/src/node_status_api/models.rs @@ -3,8 +3,12 @@ use crate::node_status_api::utils::NodeUptimes; use crate::storage::models::NodeStatus; -use mixnet_contract_common::reward_params::Performance; -use mixnet_contract_common::{IdentityKey, MixId}; +use nym_api_requests::models::{ + GatewayStatusReportResponse, GatewayUptimeHistoryResponse, HistoricalUptimeResponse, + MixnodeStatusReportResponse, MixnodeUptimeHistoryResponse, NodePerformance, RequestError, +}; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::{IdentityKey, MixId}; use okapi::openapi3::{Responses, SchemaObject}; use rocket::http::Status; use rocket::response::{self, Responder, Response}; @@ -17,18 +21,15 @@ use schemars::gen::SchemaGenerator; use schemars::schema::{InstanceType, Schema}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use sqlx::Error; use std::convert::TryFrom; -use std::fmt::{self, Display, Formatter}; +use thiserror::Error; use time::OffsetDateTime; -use validator_api_requests::models::{ - GatewayStatusReportResponse, GatewayUptimeHistoryResponse, HistoricalUptimeResponse, - MixnodeStatusReportResponse, MixnodeUptimeHistoryResponse, RequestError, -}; -// todo: put into some error enum -#[derive(Debug)] -pub struct InvalidUptime; +#[derive(Error, Debug)] +#[error("Received uptime value was within 0-100 range (got {received})")] +pub struct InvalidUptime { + received: isize, +} // value in range 0-100 #[derive(Clone, Copy, Serialize, Deserialize, Debug, Default, JsonSchema)] @@ -56,7 +57,9 @@ impl Uptime { let uptime = ((numerator as f32 / denominator as f32) * 100.0).round() as u8; if uptime > 100 { - Err(InvalidUptime) + Err(InvalidUptime { + received: uptime as isize, + }) } else { Ok(Uptime(uptime)) } @@ -70,7 +73,9 @@ impl Uptime { let uptime = (running_sum / count as f32).round() as u8; if uptime > 100 { - Err(InvalidUptime) + Err(InvalidUptime { + received: uptime as isize, + }) } else { Ok(Uptime(uptime)) } @@ -92,7 +97,9 @@ impl TryFrom<u8> for Uptime { fn try_from(value: u8) -> Result<Self, Self::Error> { if value > 100 { - Err(InvalidUptime) + Err(InvalidUptime { + received: value as isize, + }) } else { Ok(Uptime(value)) } @@ -104,7 +111,9 @@ impl TryFrom<i64> for Uptime { fn try_from(value: i64) -> Result<Self, Self::Error> { if !(0..=100).contains(&value) { - Err(InvalidUptime) + Err(InvalidUptime { + received: value as isize, + }) } else { Ok(Uptime(value as u8)) } @@ -170,6 +179,16 @@ impl From<MixnodeStatusReport> for MixnodeStatusReportResponse { } } +impl From<MixnodeStatusReport> for NodePerformance { + fn from(report: MixnodeStatusReport) -> Self { + NodePerformance { + most_recent: report.most_recent.into(), + last_hour: report.last_hour.into(), + last_24h: report.last_day.into(), + } + } +} + #[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] pub struct GatewayStatusReport { pub(crate) identity: String, @@ -219,6 +238,16 @@ impl From<GatewayStatusReport> for GatewayStatusReportResponse { } } +impl From<GatewayStatusReport> for NodePerformance { + fn from(report: GatewayStatusReport) -> Self { + NodePerformance { + most_recent: report.most_recent.into(), + last_hour: report.last_hour.into(), + last_24h: report.last_day.into(), + } + } +} + #[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)] pub struct MixnodeUptimeHistory { pub(crate) mix_id: MixId, @@ -364,48 +393,28 @@ impl OpenApiResponderInner for ErrorResponse { } #[derive(Debug, thiserror::Error)] -pub enum ValidatorApiStorageError { - MixnodeReportNotFound(MixId), - GatewayReportNotFound(String), - MixnodeUptimeHistoryNotFound(MixId), - GatewayUptimeHistoryNotFound(String), +pub enum NymApiStorageError { + #[error("could not find status report associated with mixnode {mix_id}")] + MixnodeReportNotFound { mix_id: MixId }, + + #[error("Could not find status report associated with gateway {identity}")] + GatewayReportNotFound { identity: IdentityKey }, + + #[error("could not find uptime history associated with mixnode {mix_id}")] + MixnodeUptimeHistoryNotFound { mix_id: MixId }, + + #[error("could not find uptime history associated with gateway {identity}")] + GatewayUptimeHistoryNotFound { identity: IdentityKey }, // I don't think we want to expose errors to the user about what really happened - InternalDatabaseError(String), -} + #[error("experienced internal database error")] + InternalDatabaseError(#[from] sqlx::Error), -impl From<sqlx::Error> for ValidatorApiStorageError { - fn from(err: Error) -> Self { - ValidatorApiStorageError::InternalDatabaseError(err.to_string()) - } -} + // the same is true here (also note that the message is subtly different so we would be able to distinguish them) + #[error("experienced internal storage error")] + DatabaseInconsistency { reason: String }, -impl Display for ValidatorApiStorageError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - ValidatorApiStorageError::MixnodeReportNotFound(mix_id) => write!( - f, - "Could not find status report associated with mixnode {}", - mix_id - ), - ValidatorApiStorageError::GatewayReportNotFound(identity) => write!( - f, - "Could not find status report associated with gateway {}", - identity - ), - ValidatorApiStorageError::MixnodeUptimeHistoryNotFound(mix_id) => write!( - f, - "Could not find uptime history associated with mixnode {}", - mix_id - ), - ValidatorApiStorageError::GatewayUptimeHistoryNotFound(identity) => write!( - f, - "Could not find uptime history associated with gateway {}", - identity - ), - ValidatorApiStorageError::InternalDatabaseError(err) => { - write!(f, "The internal database has experienced an issue: {err}") - } - } - } + // this one would never be returned to users since it's only possible on startup + #[error("failed to perform startup SQL migration - {0}")] + StartupMigrationFailure(#[from] sqlx::migrate::MigrateError), } diff --git a/validator-api/src/node_status_api/reward_estimate.rs b/nym-api/src/node_status_api/reward_estimate.rs similarity index 89% rename from validator-api/src/node_status_api/reward_estimate.rs rename to nym-api/src/node_status_api/reward_estimate.rs index 33bbd8320c..744ce1bb4e 100644 --- a/validator-api/src/node_status_api/reward_estimate.rs +++ b/nym-api/src/node_status_api/reward_estimate.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_std::Decimal; -use mixnet_contract_common::mixnode::MixNodeDetails; -use mixnet_contract_common::reward_params::{NodeRewardParams, Performance, RewardingParams}; -use mixnet_contract_common::rewarding::RewardEstimate; -use mixnet_contract_common::{Interval, RewardedSetNodeStatus}; +use nym_mixnet_contract_common::mixnode::MixNodeDetails; +use nym_mixnet_contract_common::reward_params::{NodeRewardParams, Performance, RewardingParams}; +use nym_mixnet_contract_common::rewarding::RewardEstimate; +use nym_mixnet_contract_common::{Interval, RewardedSetNodeStatus}; fn compute_apy(epochs_in_year: Decimal, reward: Decimal, pledge_amount: Decimal) -> Decimal { if pledge_amount.is_zero() { diff --git a/validator-api/src/node_status_api/routes.rs b/nym-api/src/node_status_api/routes.rs similarity index 63% rename from validator-api/src/node_status_api/routes.rs rename to nym-api/src/node_status_api/routes.rs index 037ca7b117..b014abef06 100644 --- a/validator-api/src/node_status_api/routes.rs +++ b/nym-api/src/node_status_api/routes.rs @@ -1,89 +1,76 @@ // Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 +use super::helpers::_get_gateways_detailed; use super::NodeStatusCache; use crate::node_status_api::helpers::{ - _compute_mixnode_reward_estimation, _get_active_set_detailed, _get_mixnode_avg_uptime, + _compute_mixnode_reward_estimation, _gateway_core_status_count, _gateway_report, + _gateway_uptime_history, _get_active_set_detailed, _get_gateway_avg_uptime, + _get_gateways_detailed_unfiltered, _get_mixnode_avg_uptime, _get_mixnode_inclusion_probabilities, _get_mixnode_inclusion_probability, _get_mixnode_reward_estimation, _get_mixnode_stake_saturation, _get_mixnode_status, - _get_mixnodes_detailed, _get_rewarded_set_detailed, _mixnode_core_status_count, - _mixnode_report, _mixnode_uptime_history, + _get_mixnodes_detailed, _get_mixnodes_detailed_unfiltered, _get_rewarded_set_detailed, + _mixnode_core_status_count, _mixnode_report, _mixnode_uptime_history, }; use crate::node_status_api::models::ErrorResponse; -use crate::storage::ValidatorApiStorage; -use crate::ValidatorCache; -use mixnet_contract_common::MixId; -use rocket::http::Status; +use crate::storage::NymApiStorage; +use crate::NymContractCache; +use nym_api_requests::models::{ + AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayBondAnnotated, + GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse, + GatewayUptimeResponse, InclusionProbabilityResponse, MixNodeBondAnnotated, + MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse, + MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse, + UptimeResponse, +}; +use nym_mixnet_contract_common::MixId; use rocket::serde::json::Json; use rocket::State; use rocket_okapi::openapi; -use validator_api_requests::models::{ - AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayCoreStatusResponse, - GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse, - MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse, - MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse, - StakeSaturationResponse, UptimeResponse, -}; #[openapi(tag = "status")] #[get("/gateway/<identity>/report")] pub(crate) async fn gateway_report( - storage: &State<ValidatorApiStorage>, + cache: &State<NodeStatusCache>, identity: &str, ) -> Result<Json<GatewayStatusReportResponse>, ErrorResponse> { - storage - .construct_gateway_report(identity) - .await - .map(GatewayStatusReportResponse::from) - .map(Json) - .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) + Ok(Json(_gateway_report(cache, identity).await?)) } #[openapi(tag = "status")] #[get("/gateway/<identity>/history")] pub(crate) async fn gateway_uptime_history( - storage: &State<ValidatorApiStorage>, + storage: &State<NymApiStorage>, identity: &str, ) -> Result<Json<GatewayUptimeHistoryResponse>, ErrorResponse> { - storage - .get_gateway_uptime_history(identity) - .await - .map(GatewayUptimeHistoryResponse::from) - .map(Json) - .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) + Ok(Json(_gateway_uptime_history(storage, identity).await?)) } #[openapi(tag = "status")] #[get("/gateway/<identity>/core-status-count?<since>")] pub(crate) async fn gateway_core_status_count( - storage: &State<ValidatorApiStorage>, + storage: &State<NymApiStorage>, identity: &str, since: Option<i64>, -) -> Json<GatewayCoreStatusResponse> { - let count = storage - .get_core_gateway_status_count(identity, since) - .await - .unwrap_or_default(); - - Json(GatewayCoreStatusResponse { - identity: identity.to_string(), - count, - }) +) -> Result<Json<GatewayCoreStatusResponse>, ErrorResponse> { + Ok(Json( + _gateway_core_status_count(storage, identity, since).await?, + )) } #[openapi(tag = "status")] #[get("/mixnode/<mix_id>/report")] pub(crate) async fn mixnode_report( - storage: &State<ValidatorApiStorage>, + cache: &State<NodeStatusCache>, mix_id: MixId, ) -> Result<Json<MixnodeStatusReportResponse>, ErrorResponse> { - Ok(Json(_mixnode_report(storage, mix_id).await?)) + Ok(Json(_mixnode_report(cache, mix_id).await?)) } #[openapi(tag = "status")] #[get("/mixnode/<mix_id>/history")] pub(crate) async fn mixnode_uptime_history( - storage: &State<ValidatorApiStorage>, + storage: &State<NymApiStorage>, mix_id: MixId, ) -> Result<Json<MixnodeUptimeHistoryResponse>, ErrorResponse> { Ok(Json(_mixnode_uptime_history(storage, mix_id).await?)) @@ -92,7 +79,7 @@ pub(crate) async fn mixnode_uptime_history( #[openapi(tag = "status")] #[get("/mixnode/<mix_id>/core-status-count?<since>")] pub(crate) async fn mixnode_core_status_count( - storage: &State<ValidatorApiStorage>, + storage: &State<NymApiStorage>, mix_id: MixId, since: Option<i64>, ) -> Result<Json<MixnodeCoreStatusResponse>, ErrorResponse> { @@ -104,7 +91,7 @@ pub(crate) async fn mixnode_core_status_count( #[openapi(tag = "status")] #[get("/mixnode/<mix_id>/status")] pub(crate) async fn get_mixnode_status( - cache: &State<ValidatorCache>, + cache: &State<NymContractCache>, mix_id: MixId, ) -> Json<MixnodeStatusResponse> { Json(_get_mixnode_status(cache, mix_id).await) @@ -114,7 +101,7 @@ pub(crate) async fn get_mixnode_status( #[get("/mixnode/<mix_id>/reward-estimation")] pub(crate) async fn get_mixnode_reward_estimation( cache: &State<NodeStatusCache>, - validator_cache: &State<ValidatorCache>, + validator_cache: &State<NymContractCache>, mix_id: MixId, ) -> Result<Json<RewardEstimationResponse>, ErrorResponse> { Ok(Json( @@ -130,7 +117,7 @@ pub(crate) async fn get_mixnode_reward_estimation( pub(crate) async fn compute_mixnode_reward_estimation( user_reward_param: Json<ComputeRewardEstParam>, cache: &State<NodeStatusCache>, - validator_cache: &State<ValidatorCache>, + validator_cache: &State<NymContractCache>, mix_id: MixId, ) -> Result<Json<RewardEstimationResponse>, ErrorResponse> { Ok(Json( @@ -148,7 +135,7 @@ pub(crate) async fn compute_mixnode_reward_estimation( #[get("/mixnode/<mix_id>/stake-saturation")] pub(crate) async fn get_mixnode_stake_saturation( cache: &State<NodeStatusCache>, - validator_cache: &State<ValidatorCache>, + validator_cache: &State<NymContractCache>, mix_id: MixId, ) -> Result<Json<StakeSaturationResponse>, ErrorResponse> { Ok(Json( @@ -170,11 +157,19 @@ pub(crate) async fn get_mixnode_inclusion_probability( #[openapi(tag = "status")] #[get("/mixnode/<mix_id>/avg_uptime")] pub(crate) async fn get_mixnode_avg_uptime( - cache: &State<ValidatorCache>, - storage: &State<ValidatorApiStorage>, + cache: &State<NodeStatusCache>, mix_id: MixId, ) -> Result<Json<UptimeResponse>, ErrorResponse> { - Ok(Json(_get_mixnode_avg_uptime(cache, storage, mix_id).await?)) + Ok(Json(_get_mixnode_avg_uptime(cache, mix_id).await?)) +} + +#[openapi(tag = "status")] +#[get("/gateway/<identity>/avg_uptime")] +pub(crate) async fn get_gateway_avg_uptime( + cache: &State<NodeStatusCache>, + identity: &str, +) -> Result<Json<GatewayUptimeResponse>, ErrorResponse> { + Ok(Json(_get_gateway_avg_uptime(cache, identity).await?)) } #[openapi(tag = "status")] @@ -193,6 +188,14 @@ pub async fn get_mixnodes_detailed( Json(_get_mixnodes_detailed(cache).await) } +#[openapi(tag = "status")] +#[get("/mixnodes/detailed-unfiltered")] +pub async fn get_mixnodes_detailed_unfiltered( + cache: &State<NodeStatusCache>, +) -> Json<Vec<MixNodeBondAnnotated>> { + Json(_get_mixnodes_detailed_unfiltered(cache).await) +} + #[openapi(tag = "status")] #[get("/mixnodes/rewarded/detailed")] pub async fn get_rewarded_set_detailed( @@ -208,3 +211,19 @@ pub async fn get_active_set_detailed( ) -> Json<Vec<MixNodeBondAnnotated>> { Json(_get_active_set_detailed(cache).await) } + +#[openapi(tag = "status")] +#[get("/gateways/detailed")] +pub async fn get_gateways_detailed( + cache: &State<NodeStatusCache>, +) -> Json<Vec<GatewayBondAnnotated>> { + Json(_get_gateways_detailed(cache).await) +} + +#[openapi(tag = "status")] +#[get("/gateways/detailed-unfiltered")] +pub async fn get_gateways_detailed_unfiltered( + cache: &State<NodeStatusCache>, +) -> Json<Vec<GatewayBondAnnotated>> { + Json(_get_gateways_detailed_unfiltered(cache).await) +} diff --git a/validator-api/src/node_status_api/uptime_updater.rs b/nym-api/src/node_status_api/uptime_updater.rs similarity index 85% rename from validator-api/src/node_status_api/uptime_updater.rs rename to nym-api/src/node_status_api/uptime_updater.rs index dcb4f6424c..78d4116e11 100644 --- a/validator-api/src/node_status_api/uptime_updater.rs +++ b/nym-api/src/node_status_api/uptime_updater.rs @@ -2,22 +2,22 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node_status_api::models::{ - GatewayStatusReport, MixnodeStatusReport, ValidatorApiStorageError, + GatewayStatusReport, MixnodeStatusReport, NymApiStorageError, }; use crate::node_status_api::ONE_DAY; -use crate::storage::ValidatorApiStorage; +use crate::storage::NymApiStorage; use log::error; +use nym_task::{TaskClient, TaskManager}; use std::time::Duration; -use task::ShutdownListener; use time::{OffsetDateTime, PrimitiveDateTime, Time}; use tokio::time::{interval, sleep}; pub(crate) struct HistoricalUptimeUpdater { - storage: ValidatorApiStorage, + storage: NymApiStorage, } impl HistoricalUptimeUpdater { - pub(crate) fn new(storage: ValidatorApiStorage) -> Self { + pub(crate) fn new(storage: NymApiStorage) -> Self { HistoricalUptimeUpdater { storage } } @@ -30,8 +30,7 @@ impl HistoricalUptimeUpdater { async fn get_active_nodes( &self, now: OffsetDateTime, - ) -> Result<(Vec<MixnodeStatusReport>, Vec<GatewayStatusReport>), ValidatorApiStorageError> - { + ) -> Result<(Vec<MixnodeStatusReport>, Vec<GatewayStatusReport>), NymApiStorageError> { let day_ago = (now - ONE_DAY).unix_timestamp(); let active_mixnodes = self .storage @@ -46,7 +45,7 @@ impl HistoricalUptimeUpdater { Ok((active_mixnodes, active_gateways)) } - async fn update_uptimes(&self) -> Result<(), ValidatorApiStorageError> { + async fn update_uptimes(&self) -> Result<(), NymApiStorageError> { let now = OffsetDateTime::now_utc(); let today_iso_8601 = now.date().to_string(); @@ -69,7 +68,7 @@ impl HistoricalUptimeUpdater { Ok(()) } - pub(crate) async fn run(&self, mut shutdown: ShutdownListener) { + pub(crate) async fn run(&self, mut shutdown: TaskClient) { // update uptimes at 23:00 UTC each day so that we'd have data from the actual [almost] whole day // and so that we would avoid the edge case of starting validator API at 23:59 and having some // nodes update for different days @@ -118,4 +117,10 @@ impl HistoricalUptimeUpdater { } } } + + pub(crate) fn start(storage: &NymApiStorage, shutdown: &TaskManager) { + let uptime_updater = HistoricalUptimeUpdater::new(storage.to_owned()); + let shutdown_listener = shutdown.subscribe(); + tokio::spawn(async move { uptime_updater.run(shutdown_listener).await }); + } } diff --git a/validator-api/src/node_status_api/utils.rs b/nym-api/src/node_status_api/utils.rs similarity index 99% rename from validator-api/src/node_status_api/utils.rs rename to nym-api/src/node_status_api/utils.rs index 72f3fc3785..da824188b9 100644 --- a/validator-api/src/node_status_api/utils.rs +++ b/nym-api/src/node_status_api/utils.rs @@ -5,7 +5,7 @@ use crate::node_status_api::models::Uptime; use crate::node_status_api::{FIFTEEN_MINUTES, ONE_HOUR}; use crate::storage::models::NodeStatus; use log::warn; -use mixnet_contract_common::MixId; +use nym_mixnet_contract_common::MixId; use std::convert::TryInto; use time::OffsetDateTime; diff --git a/nym-api/src/nym_contract_cache/cache/data.rs b/nym-api/src/nym_contract_cache/cache/data.rs new file mode 100644 index 0000000000..2188373585 --- /dev/null +++ b/nym-api/src/nym_contract_cache/cache/data.rs @@ -0,0 +1,41 @@ +// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::support::caching::Cache; +use nym_mixnet_contract_common::{ + families::FamilyHead, GatewayBond, IdentityKey, Interval, MixId, MixNodeDetails, + RewardingParams, +}; +use std::collections::HashSet; + +pub(crate) struct ValidatorCacheData { + pub(crate) mixnodes: Cache<Vec<MixNodeDetails>>, + pub(crate) gateways: Cache<Vec<GatewayBond>>, + + pub(crate) mixnodes_blacklist: Cache<HashSet<MixId>>, + pub(crate) gateways_blacklist: Cache<HashSet<IdentityKey>>, + + pub(crate) rewarded_set: Cache<Vec<MixNodeDetails>>, + pub(crate) active_set: Cache<Vec<MixNodeDetails>>, + + pub(crate) current_reward_params: Cache<Option<RewardingParams>>, + pub(crate) current_interval: Cache<Option<Interval>>, + + pub(crate) mix_to_family: Cache<Vec<(IdentityKey, FamilyHead)>>, +} + +impl ValidatorCacheData { + pub(crate) fn new() -> Self { + ValidatorCacheData { + mixnodes: Cache::default(), + gateways: Cache::default(), + rewarded_set: Cache::default(), + active_set: Cache::default(), + mixnodes_blacklist: Cache::default(), + gateways_blacklist: Cache::default(), + current_interval: Cache::default(), + current_reward_params: Cache::default(), + mix_to_family: Cache::default(), + } + } +} diff --git a/nym-api/src/nym_contract_cache/cache/mod.rs b/nym-api/src/nym_contract_cache/cache/mod.rs new file mode 100644 index 0000000000..cd885f7a5d --- /dev/null +++ b/nym-api/src/nym_contract_cache/cache/mod.rs @@ -0,0 +1,304 @@ +use crate::support::caching::Cache; +use data::ValidatorCacheData; +use nym_api_requests::models::MixnodeStatus; +use nym_mixnet_contract_common::{ + families::FamilyHead, GatewayBond, IdentityKey, Interval, MixId, MixNodeBond, MixNodeDetails, + RewardingParams, +}; +use rocket::fairing::AdHoc; +use std::{ + collections::HashSet, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, +}; +use tokio::sync::RwLock; +use tokio::time; + +mod data; +pub(crate) mod refresher; + +#[derive(Clone)] +pub struct NymContractCache { + pub(crate) initialised: Arc<AtomicBool>, + pub(crate) inner: Arc<RwLock<ValidatorCacheData>>, +} + +impl NymContractCache { + fn new() -> Self { + NymContractCache { + initialised: Arc::new(AtomicBool::new(false)), + inner: Arc::new(RwLock::new(ValidatorCacheData::new())), + } + } + + pub fn stage() -> AdHoc { + AdHoc::on_ignite("Validator Cache Stage", |rocket| async { + rocket.manage(Self::new()) + }) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) async fn update( + &self, + mixnodes: Vec<MixNodeDetails>, + gateways: Vec<GatewayBond>, + rewarded_set: Vec<MixNodeDetails>, + active_set: Vec<MixNodeDetails>, + rewarding_params: RewardingParams, + current_interval: Interval, + mix_to_family: Vec<(IdentityKey, FamilyHead)>, + ) { + match time::timeout(Duration::from_millis(100), self.inner.write()).await { + Ok(mut cache) => { + cache.mixnodes.update(mixnodes); + cache.gateways.update(gateways); + cache.rewarded_set.update(rewarded_set); + cache.active_set.update(active_set); + cache.current_reward_params.update(Some(rewarding_params)); + cache.current_interval.update(Some(current_interval)); + cache.mix_to_family.update(mix_to_family) + } + Err(err) => { + error!("{err}"); + } + } + } + + pub async fn mixnodes_blacklist(&self) -> Cache<HashSet<MixId>> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.mixnodes_blacklist.clone(), + Err(err) => { + error!("{err}"); + Cache::new(HashSet::new()) + } + } + } + + pub async fn gateways_blacklist(&self) -> Cache<HashSet<IdentityKey>> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.gateways_blacklist.clone(), + Err(err) => { + error!("{err}"); + Cache::new(HashSet::new()) + } + } + } + + pub async fn update_mixnodes_blacklist(&self, add: HashSet<MixId>, remove: HashSet<MixId>) { + let blacklist = self.mixnodes_blacklist().await; + let mut blacklist = blacklist + .value + .union(&add) + .cloned() + .collect::<HashSet<MixId>>(); + let to_remove = blacklist + .intersection(&remove) + .cloned() + .collect::<HashSet<MixId>>(); + for key in to_remove { + blacklist.remove(&key); + } + match time::timeout(Duration::from_millis(100), self.inner.write()).await { + Ok(mut cache) => { + cache.mixnodes_blacklist.update(blacklist); + } + Err(err) => { + error!("Failed to update mixnodes blacklist: {err}"); + } + } + } + + pub async fn update_gateways_blacklist( + &self, + add: HashSet<IdentityKey>, + remove: HashSet<IdentityKey>, + ) { + let blacklist = self.gateways_blacklist().await; + let mut blacklist = blacklist + .value + .union(&add) + .cloned() + .collect::<HashSet<IdentityKey>>(); + let to_remove = blacklist + .intersection(&remove) + .cloned() + .collect::<HashSet<IdentityKey>>(); + for key in to_remove { + blacklist.remove(&key); + } + match time::timeout(Duration::from_millis(100), self.inner.write()).await { + Ok(mut cache) => { + cache.gateways_blacklist.update(blacklist); + } + Err(err) => { + error!("Failed to update gateways blacklist: {err}"); + } + } + } + + pub async fn mixnodes_filtered(&self) -> Vec<MixNodeDetails> { + let mixnodes = self.mixnodes_all().await; + if mixnodes.is_empty() { + return Vec::new(); + } + let blacklist = self.mixnodes_blacklist().await; + + if !blacklist.is_empty() { + mixnodes + .into_iter() + .filter(|mix| !blacklist.value.contains(&mix.mix_id())) + .collect() + } else { + mixnodes + } + } + + pub async fn mixnodes_all(&self) -> Vec<MixNodeDetails> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.mixnodes.clone().value, + Err(err) => { + error!("{err}"); + Vec::new() + } + } + } + + pub async fn mixnodes_basic(&self) -> Vec<MixNodeBond> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache + .mixnodes + .clone() + .into_inner() + .into_iter() + .map(|bond| bond.bond_information) + .collect(), + Err(err) => { + error!("{err}"); + Vec::new() + } + } + } + + pub async fn gateways_filtered(&self) -> Vec<GatewayBond> { + let gateways = self.gateways_all().await; + if gateways.is_empty() { + return Vec::new(); + } + + let blacklist = self.gateways_blacklist().await; + + if !blacklist.is_empty() { + gateways + .into_iter() + .filter(|mix| !blacklist.value.contains(mix.identity())) + .collect() + } else { + gateways + } + } + + pub async fn gateways_all(&self) -> Vec<GatewayBond> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.gateways.value.clone(), + Err(err) => { + error!("{err}"); + Vec::new() + } + } + } + + pub async fn rewarded_set(&self) -> Cache<Vec<MixNodeDetails>> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.rewarded_set.clone(), + Err(err) => { + error!("{err}"); + Cache::new(Vec::new()) + } + } + } + + pub async fn active_set(&self) -> Cache<Vec<MixNodeDetails>> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.active_set.clone(), + Err(err) => { + error!("{err}"); + Cache::new(Vec::new()) + } + } + } + + pub async fn mix_to_family(&self) -> Cache<Vec<(IdentityKey, FamilyHead)>> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.mix_to_family.clone(), + Err(err) => { + error!("{err}"); + Cache::new(Vec::new()) + } + } + } + + pub(crate) async fn interval_reward_params(&self) -> Cache<Option<RewardingParams>> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.current_reward_params.clone(), + Err(err) => { + error!("{err}"); + Cache::new(None) + } + } + } + + pub(crate) async fn current_interval(&self) -> Cache<Option<Interval>> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.current_interval.clone(), + Err(err) => { + error!("{err}"); + Cache::new(None) + } + } + } + + pub async fn mixnode_details(&self, mix_id: MixId) -> (Option<MixNodeDetails>, MixnodeStatus) { + // it might not be the most optimal to possibly iterate the entire vector to find (or not) + // the relevant value. However, the vectors are relatively small (< 10_000 elements, < 1000 for active set) + + let active_set = &self.active_set().await.value; + if let Some(bond) = active_set.iter().find(|mix| mix.mix_id() == mix_id) { + return (Some(bond.clone()), MixnodeStatus::Active); + } + + let rewarded_set = &self.rewarded_set().await.value; + if let Some(bond) = rewarded_set.iter().find(|mix| mix.mix_id() == mix_id) { + return (Some(bond.clone()), MixnodeStatus::Standby); + } + + let all_bonded = &self.mixnodes_filtered().await; + if let Some(bond) = all_bonded.iter().find(|mix| mix.mix_id() == mix_id) { + (Some(bond.clone()), MixnodeStatus::Inactive) + } else { + (None, MixnodeStatus::NotFound) + } + } + + pub async fn mixnode_status(&self, mix_id: MixId) -> MixnodeStatus { + self.mixnode_details(mix_id).await.1 + } + + pub fn initialised(&self) -> bool { + self.initialised.load(Ordering::Relaxed) + } + + pub(crate) async fn wait_for_initial_values(&self) { + let initialisation_backoff = Duration::from_secs(5); + loop { + if self.initialised() { + break; + } else { + debug!("Validator cache hasn't been initialised yet - waiting for {:?} before trying again", initialisation_backoff); + tokio::time::sleep(initialisation_backoff).await; + } + } + } +} diff --git a/nym-api/src/nym_contract_cache/cache/refresher.rs b/nym-api/src/nym_contract_cache/cache/refresher.rs new file mode 100644 index 0000000000..4f3241a7aa --- /dev/null +++ b/nym-api/src/nym_contract_cache/cache/refresher.rs @@ -0,0 +1,133 @@ +use super::NymContractCache; +use crate::nyxd::Client; +use crate::support::caching::CacheNotification; +use anyhow::Result; +use nym_mixnet_contract_common::{MixId, MixNodeDetails, RewardedSetNodeStatus}; +use nym_task::TaskClient; +use std::{collections::HashMap, sync::atomic::Ordering, time::Duration}; +use tokio::sync::watch; +use tokio::time; + +pub struct NymContractCacheRefresher { + nyxd_client: Client, + cache: NymContractCache, + caching_interval: Duration, + + // Notify listeners that the cache has been updated + update_notifier: watch::Sender<CacheNotification>, +} + +impl NymContractCacheRefresher { + pub(crate) fn new( + nyxd_client: Client, + caching_interval: Duration, + cache: NymContractCache, + ) -> Self { + let (tx, _) = watch::channel(CacheNotification::Start); + NymContractCacheRefresher { + nyxd_client, + cache, + caching_interval, + update_notifier: tx, + } + } + + pub fn subscribe(&self) -> watch::Receiver<CacheNotification> { + self.update_notifier.subscribe() + } + + async fn refresh(&self) -> Result<()> { + let rewarding_params = self.nyxd_client.get_current_rewarding_parameters().await?; + let current_interval = self.nyxd_client.get_current_interval().await?.interval; + + let mixnodes = self.nyxd_client.get_mixnodes().await?; + let gateways = self.nyxd_client.get_gateways().await?; + + let mix_to_family = self.nyxd_client.get_all_family_members().await?; + + let rewarded_set_map = self.get_rewarded_set_map().await; + + let (rewarded_set, active_set) = + Self::collect_rewarded_and_active_set_details(&mixnodes, &rewarded_set_map); + + info!( + "Updating validator cache. There are {} mixnodes and {} gateways", + mixnodes.len(), + gateways.len(), + ); + + self.cache + .update( + mixnodes, + gateways, + rewarded_set, + active_set, + rewarding_params, + current_interval, + mix_to_family, + ) + .await; + + if let Err(err) = self.update_notifier.send(CacheNotification::Updated) { + warn!("Failed to notify validator cache refresh: {err}"); + } + + Ok(()) + } + + async fn get_rewarded_set_map(&self) -> HashMap<MixId, RewardedSetNodeStatus> { + self.nyxd_client + .get_rewarded_set_mixnodes() + .await + .map(|nodes| nodes.into_iter().collect()) + .unwrap_or_default() + } + + fn collect_rewarded_and_active_set_details( + all_mixnodes: &[MixNodeDetails], + rewarded_set_nodes: &HashMap<MixId, RewardedSetNodeStatus>, + ) -> (Vec<MixNodeDetails>, Vec<MixNodeDetails>) { + let mut active_set = Vec::new(); + let mut rewarded_set = Vec::new(); + + for mix in all_mixnodes { + if let Some(status) = rewarded_set_nodes.get(&mix.mix_id()) { + rewarded_set.push(mix.clone()); + if status.is_active() { + active_set.push(mix.clone()) + } + } + } + + (rewarded_set, active_set) + } + + pub(crate) async fn run(&self, mut shutdown: TaskClient) { + let mut interval = time::interval(self.caching_interval); + while !shutdown.is_shutdown() { + tokio::select! { + _ = interval.tick() => { + tokio::select! { + biased; + _ = shutdown.recv() => { + trace!("ValidatorCacheRefresher: Received shutdown"); + } + ret = self.refresh() => { + if let Err(err) = ret { + error!("Failed to refresh validator cache - {err}"); + } else { + // relaxed memory ordering is fine here. worst case scenario network monitor + // will just have to wait for an additional backoff to see the change. + // And so this will not really incur any performance penalties by setting it every loop iteration + self.cache.initialised.store(true, Ordering::Relaxed) + } + } + } + } + _ = shutdown.recv() => { + trace!("ValidatorCacheRefresher: Received shutdown"); + } + } + } + } +} diff --git a/nym-api/src/nym_contract_cache/mod.rs b/nym-api/src/nym_contract_cache/mod.rs new file mode 100644 index 0000000000..e3f4ef267c --- /dev/null +++ b/nym-api/src/nym_contract_cache/mod.rs @@ -0,0 +1,50 @@ +// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::nym_contract_cache::cache::NymContractCache; +use crate::support::config::Config; +use crate::support::{self, nyxd}; +use nym_task::TaskManager; +use okapi::openapi3::OpenApi; +use rocket::Route; +use rocket_okapi::openapi_get_routes_spec; +use rocket_okapi::settings::OpenApiSettings; + +use self::cache::refresher::NymContractCacheRefresher; + +pub(crate) mod cache; +pub(crate) mod routes; + +pub(crate) fn nym_contract_cache_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) { + openapi_get_routes_spec![ + settings: routes::get_mixnodes, + routes::get_mixnodes_detailed, + routes::get_gateways, + routes::get_active_set, + routes::get_active_set_detailed, + routes::get_rewarded_set, + routes::get_rewarded_set_detailed, + routes::get_blacklisted_mixnodes, + routes::get_blacklisted_gateways, + routes::get_interval_reward_params, + routes::get_current_epoch + ] +} + +pub(crate) fn start_refresher( + config: &Config, + nym_contract_cache_state: &NymContractCache, + nyxd_client: nyxd::Client, + shutdown: &TaskManager, +) -> tokio::sync::watch::Receiver<support::caching::CacheNotification> { + let nym_contract_cache_refresher = NymContractCacheRefresher::new( + nyxd_client, + config.get_topology_caching_interval(), + nym_contract_cache_state.to_owned(), + ); + let nym_contract_cache_listener = nym_contract_cache_refresher.subscribe(); + let shutdown_listener = shutdown.subscribe(); + tokio::spawn(async move { nym_contract_cache_refresher.run(shutdown_listener).await }); + + nym_contract_cache_listener +} diff --git a/validator-api/src/contract_cache/routes.rs b/nym-api/src/nym_contract_cache/routes.rs similarity index 72% rename from validator-api/src/contract_cache/routes.rs rename to nym-api/src/nym_contract_cache/routes.rs index 6aba1ba4f9..bdf268ddfc 100644 --- a/validator-api/src/contract_cache/routes.rs +++ b/nym-api/src/nym_contract_cache/routes.rs @@ -2,16 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - contract_cache::ValidatorCache, node_status_api::{ helpers::{_get_active_set_detailed, _get_mixnodes_detailed, _get_rewarded_set_detailed}, NodeStatusCache, }, + nym_contract_cache::cache::NymContractCache, }; -use mixnet_contract_common::{ +use nym_api_requests::models::MixNodeBondAnnotated; +use nym_mixnet_contract_common::{ mixnode::MixNodeDetails, reward_params::RewardingParams, GatewayBond, Interval, MixId, }; -use validator_api_requests::models::MixNodeBondAnnotated; use rocket::{serde::json::Json, State}; use rocket_okapi::openapi; @@ -19,8 +19,8 @@ use std::collections::HashSet; #[openapi(tag = "contract-cache")] #[get("/mixnodes")] -pub async fn get_mixnodes(cache: &State<ValidatorCache>) -> Json<Vec<MixNodeDetails>> { - Json(cache.mixnodes().await) +pub async fn get_mixnodes(cache: &State<NymContractCache>) -> Json<Vec<MixNodeDetails>> { + Json(cache.mixnodes_filtered().await) } // DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated, @@ -40,13 +40,13 @@ pub async fn get_mixnodes_detailed( #[openapi(tag = "contract-cache")] #[get("/gateways")] -pub async fn get_gateways(cache: &State<ValidatorCache>) -> Json<Vec<GatewayBond>> { - Json(cache.gateways().await) +pub async fn get_gateways(cache: &State<NymContractCache>) -> Json<Vec<GatewayBond>> { + Json(cache.gateways_filtered().await) } #[openapi(tag = "contract-cache")] #[get("/mixnodes/rewarded")] -pub async fn get_rewarded_set(cache: &State<ValidatorCache>) -> Json<Vec<MixNodeDetails>> { +pub async fn get_rewarded_set(cache: &State<NymContractCache>) -> Json<Vec<MixNodeDetails>> { Json(cache.rewarded_set().await.value) } @@ -67,8 +67,8 @@ pub async fn get_rewarded_set_detailed( #[openapi(tag = "contract-cache")] #[get("/mixnodes/active")] -pub async fn get_active_set(cache: &State<ValidatorCache>) -> Json<Vec<MixNodeDetails>> { - Json(cache.active_set().await.value) +pub async fn get_active_set(cache: &State<NymContractCache>) -> Json<Vec<MixNodeDetails>> { + Json(cache.mixnodes_filtered().await) } // DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated, @@ -89,29 +89,39 @@ pub async fn get_active_set_detailed( #[openapi(tag = "contract-cache")] #[get("/mixnodes/blacklisted")] pub async fn get_blacklisted_mixnodes( - cache: &State<ValidatorCache>, + cache: &State<NymContractCache>, ) -> Json<Option<HashSet<MixId>>> { - Json(cache.mixnodes_blacklist().await.map(|c| c.value)) + let blacklist = cache.mixnodes_blacklist().await.value; + if blacklist.is_empty() { + Json(None) + } else { + Json(Some(blacklist)) + } } #[openapi(tag = "contract-cache")] #[get("/gateways/blacklisted")] pub async fn get_blacklisted_gateways( - cache: &State<ValidatorCache>, + cache: &State<NymContractCache>, ) -> Json<Option<HashSet<String>>> { - Json(cache.gateways_blacklist().await.map(|c| c.value)) + let blacklist = cache.gateways_blacklist().await.value; + if blacklist.is_empty() { + Json(None) + } else { + Json(Some(blacklist)) + } } #[openapi(tag = "contract-cache")] #[get("/epoch/reward_params")] pub async fn get_interval_reward_params( - cache: &State<ValidatorCache>, + cache: &State<NymContractCache>, ) -> Json<Option<RewardingParams>> { Json(cache.interval_reward_params().await.value) } #[openapi(tag = "contract-cache")] #[get("/epoch/current")] -pub async fn get_current_epoch(cache: &State<ValidatorCache>) -> Json<Option<Interval>> { +pub async fn get_current_epoch(cache: &State<NymContractCache>) -> Json<Option<Interval>> { Json(cache.current_interval().await.value) } diff --git a/nym-api/src/support/caching/mod.rs b/nym-api/src/support/caching/mod.rs new file mode 100644 index 0000000000..ad2e40e80d --- /dev/null +++ b/nym-api/src/support/caching/mod.rs @@ -0,0 +1,51 @@ +use serde::Serialize; +use std::ops::Deref; +use time::OffsetDateTime; + +#[derive(Default, Serialize, Clone)] +pub struct Cache<T> { + pub value: T, + as_at: i64, +} + +impl<T: Clone> Cache<T> { + pub fn new(value: T) -> Self { + Cache { + value, + as_at: current_unix_timestamp(), + } + } + + pub(crate) fn update(&mut self, value: T) { + self.value = value; + self.as_at = current_unix_timestamp() + } + + pub fn timestamp(&self) -> i64 { + self.as_at + } + + pub fn into_inner(self) -> T { + self.value + } +} + +impl<T> Deref for Cache<T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +fn current_unix_timestamp() -> i64 { + let now = OffsetDateTime::now_utc(); + now.unix_timestamp() +} + +// The cache can emit notifications to listeners about the current state +#[derive(Debug, PartialEq, Eq)] +pub enum CacheNotification { + Start, + Updated, +} diff --git a/nym-api/src/support/cli/mod.rs b/nym-api/src/support/cli/mod.rs new file mode 100644 index 0000000000..66cde03562 --- /dev/null +++ b/nym-api/src/support/cli/mod.rs @@ -0,0 +1,176 @@ +// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use super::config::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 nym_bin_common::build_information::BinaryBuildInformation; +use nym_config::{NymConfig, OptionalSet}; +use std::fs; +use validator_client::nyxd; + +lazy_static! { + pub static ref PRETTY_BUILD_INFORMATION: String = + BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print(); +} + +// Helper for passing LONG_VERSION to clap +fn pretty_build_info_static() -> &'static str { + &PRETTY_BUILD_INFORMATION +} + +// explicitly defined custom parser (as opposed to just using +// #[arg(value_parser = clap::value_parser!(u8).range(0..100))] +// for better error message +fn threshold_in_range(s: &str) -> Result<u8, String> { + let threshold: usize = s + .parse() + .map_err(|_| format!("`{s}` isn't a valid threshold number"))?; + if threshold > 100 { + Err(format!("{threshold} is not within the range 0-100")) + } else { + Ok(threshold as u8) + } +} + +#[derive(Parser)] +#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] +pub(crate) struct CliArgs { + /// Path pointing to an env file that configures the Nym API. + #[clap(short, long)] + pub(crate) config_env_file: Option<std::path::PathBuf>, + + /// 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<bool>, + + /// Specifies whether network rewarding is enabled on this API + #[clap(short = 'r', long, requires = "enable_monitor", requires = "mnemonic")] + pub(crate) enable_rewarding: Option<bool>, + + /// Endpoint to nyxd instance from which the monitor will grab nodes to test + #[clap(long)] + pub(crate) nyxd_validator: Option<url::Url>, + + /// Address of the mixnet contract managing the network + #[clap(long)] + pub(crate) mixnet_contract: Option<nyxd::AccountId>, + + /// Address of the vesting contract holding locked tokens + #[clap(long)] + pub(crate) vesting_contract: Option<nyxd::AccountId>, + + /// Mnemonic of the network monitor used for rewarding operators + // even though we're currently converting the mnemonic to string (and then back to the concrete type) + // at least we're getting immediate validation when passing the arguments + #[clap(long)] + pub(crate) mnemonic: Option<bip39::Mnemonic>, + + /// Specifies whether a config file based on provided arguments should be saved to a file + #[clap(short = 'w', long)] + pub(crate) save_config: bool, + + /// Specifies the minimum percentage of monitor test run data present in order to distribute rewards for given interval. + #[clap(long, value_parser = threshold_in_range)] + pub(crate) monitor_threshold: Option<u8>, + + /// Mixnodes with reliability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set. + #[clap(long, value_parser = threshold_in_range)] + pub(crate) min_mixnode_reliability: Option<u8>, + + /// Gateways with reliability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set. + #[clap(long, value_parser = threshold_in_range)] + pub(crate) min_gateway_reliability: Option<u8>, + + /// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement + #[clap(long)] + pub(crate) enabled_credentials_mode: Option<bool>, + + /// Announced address where coconut clients will connect. + #[clap(long, hide = true)] + pub(crate) announce_address: Option<url::Url>, + + /// Flag to indicate whether coconut signer authority is enabled on this API + #[clap( + long, + requires = "mnemonic", + requires = "announce_address", + hide = true + )] + pub(crate) enable_coconut: Option<bool>, +} + +pub(crate) fn build_config(args: CliArgs) -> Result<Config> { + let id = args.id.clone(); + + // try to load config from the file, if it doesn't exist, use default values + let (config_from_file, already_initialized) = match Config::load_from_file(&id) { + Ok(cfg) => (cfg, true), + Err(_) => { + let config_path = Config::default_config_file_path(&id) + .into_os_string() + .into_string() + .unwrap(); + warn!( + "Could not load the configuration file from {}. Either the file did not exist or was malformed. Using the default values instead", + config_path + ); + (Config::new(), false) + } + }; + + let config = override_config(config_from_file, args); + + if !already_initialized { + fs::create_dir_all(Config::default_config_directory(&id)) + .expect("Could not create config directory"); + fs::create_dir_all(Config::default_data_directory(&id)) + .expect("Could not create data directory"); + crate::coconut::dkg::controller::init_keypair(&config)?; + } + + Ok(config) +} + +pub(crate) fn override_config(config: Config, args: CliArgs) -> Config { + config + .with_id(&args.id) + .with_optional(Config::with_custom_nyxd_validator, args.nyxd_validator) + .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_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) +} diff --git a/validator-api/src/config/mod.rs b/nym-api/src/support/config/mod.rs similarity index 74% rename from validator-api/src/config/mod.rs rename to nym-api/src/support/config/mod.rs index d38acfb546..e9d8f02914 100644 --- a/validator-api/src/config/mod.rs +++ b/nym-api/src/support/config/mod.rs @@ -1,14 +1,15 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crate::config::template::config_template; -use config::defaults::mainnet::MIXNET_CONTRACT_ADDRESS; -use config::defaults::DEFAULT_VALIDATOR_API_PORT; -use config::NymConfig; +use self::template::config_template; +use nym_config::defaults::mainnet::{MIXNET_CONTRACT_ADDRESS, VESTING_CONTRACT_ADDRESS}; +use nym_config::defaults::DEFAULT_NYM_API_PORT; +use nym_config::NymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::time::Duration; use url::Url; +use validator_client::nyxd; mod template; @@ -25,14 +26,17 @@ const DEFAULT_GATEWAY_PING_INTERVAL: Duration = Duration::from_secs(60); // 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); -const DEFAULT_GATEWAY_CONNECTION_TIMEOUT: Duration = Duration::from_millis(2_500); +// 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_CACHE_INTERVAL: Duration = Duration::from_secs(30); +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; @@ -51,6 +55,9 @@ pub struct Config { #[serde(default)] topology_cacher: TopologyCacher, + #[serde(default)] + circulating_supply_cacher: CirculatingSupplyCacher, + #[serde(default)] rewarding: Rewarding, @@ -67,11 +74,11 @@ impl NymConfig for Config { dirs::home_dir() .expect("Failed to evaluate $HOME value") .join(".nym") - .join("validator-api") + .join("nym-api") } fn try_default_root_directory() -> Option<PathBuf> { - dirs::home_dir().map(|path| path.join(".nym").join("validator-api")) + dirs::home_dir().map(|path| path.join(".nym").join("nym-api")) } fn root_directory(&self) -> PathBuf { @@ -90,7 +97,7 @@ impl NymConfig for Config { #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] #[serde(default)] pub struct Base { - /// ID specifies the human readable ID of this particular validator-api. + /// ID specifies the human readable ID of this particular nym-api. id: String, local_validator: Url, @@ -101,10 +108,13 @@ pub struct Base { announce_address: Url, /// Address of the validator contract managing the network - mixnet_contract_address: String, + 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: String, + mnemonic: bip39::Mnemonic, } impl Default for Base { @@ -114,14 +124,16 @@ impl Default for Base { .expect("default local validator is malformed!"); let mut default_announce_address = default_validator.clone(); default_announce_address - .set_port(Some(DEFAULT_VALIDATOR_API_PORT)) + .set_port(Some(DEFAULT_NYM_API_PORT)) .expect("default local validator is malformed!"); + Base { id: String::default(), local_validator: default_validator, announce_address: default_announce_address, - mixnet_contract_address: MIXNET_CONTRACT_ADDRESS.to_string(), - mnemonic: "exact antique hybrid width raise anchor puzzle degree fee quit long crack net vague hip despair write put useless civil mechanic broom music day".to_string(), + mixnet_contract_address: MIXNET_CONTRACT_ADDRESS.parse().unwrap(), + vesting_contract_address: VESTING_CONTRACT_ADDRESS.parse().unwrap(), + mnemonic: bip39::Mnemonic::generate(24).unwrap(), } } } @@ -190,8 +202,8 @@ pub struct NetworkMonitor { impl NetworkMonitor { pub const DB_FILE: &'static str = "credentials_database.db"; - fn default_credentials_database_path() -> PathBuf { - Config::default_data_directory(None).join(Self::DB_FILE) + fn default_credentials_database_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::DB_FILE) } } @@ -209,7 +221,7 @@ impl Default for NetworkMonitor { gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, gateway_connection_timeout: DEFAULT_GATEWAY_CONNECTION_TIMEOUT, packet_delivery_timeout: DEFAULT_PACKET_DELIVERY_TIMEOUT, - credentials_database_path: Self::default_credentials_database_path(), + credentials_database_path: Default::default(), test_routes: DEFAULT_TEST_ROUTES, minimum_test_routes: DEFAULT_MINIMUM_TEST_ROUTES, route_test_packets: DEFAULT_ROUTE_TEST_PACKETS, @@ -223,20 +235,24 @@ impl Default for NetworkMonitor { pub struct NodeStatusAPI { /// Path to the database file containing uptime statuses for all mixnodes and gateways. database_path: PathBuf, + + #[serde(with = "humantime_serde")] + caching_interval: Duration, } impl NodeStatusAPI { pub const DB_FILE: &'static str = "db.sqlite"; - fn default_database_path() -> PathBuf { - Config::default_data_directory(None).join(Self::DB_FILE) + fn default_database_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::DB_FILE) } } impl Default for NodeStatusAPI { fn default() -> Self { NodeStatusAPI { - database_path: Self::default_database_path(), + database_path: Default::default(), + caching_interval: DEFAULT_NODE_STATUS_CACHE_INTERVAL, } } } @@ -251,7 +267,25 @@ pub struct TopologyCacher { impl Default for TopologyCacher { fn default() -> Self { TopologyCacher { - caching_interval: DEFAULT_CACHE_INTERVAL, + caching_interval: DEFAULT_TOPOLOGY_CACHE_INTERVAL, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct CirculatingSupplyCacher { + enabled: bool, + + #[serde(with = "humantime_serde")] + caching_interval: Duration, +} + +impl Default for CirculatingSupplyCacher { + fn default() -> Self { + CirculatingSupplyCacher { + enabled: true, + caching_interval: DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL, } } } @@ -309,24 +343,24 @@ impl CoconutSigner { pub const COCONUT_VERIFICATION_KEY_FILE: &'static str = "coconut_verification_key.pem"; pub const COCONUT_SECRET_KEY_FILE: &'static str = "coconut_secret_key.pem"; - fn default_coconut_verification_key_path() -> PathBuf { - Config::default_data_directory(None).join(Self::COCONUT_VERIFICATION_KEY_FILE) + fn default_coconut_verification_key_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::COCONUT_VERIFICATION_KEY_FILE) } - fn default_coconut_secret_key_path() -> PathBuf { - Config::default_data_directory(None).join(Self::COCONUT_SECRET_KEY_FILE) + fn default_coconut_secret_key_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::COCONUT_SECRET_KEY_FILE) } - fn default_dkg_persistent_state_path() -> PathBuf { - Config::default_data_directory(None).join(Self::DKG_PERSISTENT_STATE_FILE) + fn default_dkg_persistent_state_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::DKG_PERSISTENT_STATE_FILE) } - fn default_dkg_decryption_key_path() -> PathBuf { - Config::default_data_directory(None).join(Self::DKG_DECRYPTION_KEY_FILE) + fn default_dkg_decryption_key_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::DKG_DECRYPTION_KEY_FILE) } - fn default_dkg_public_key_with_proof_path() -> PathBuf { - Config::default_data_directory(None).join(Self::DKG_PUBLIC_KEY_WITH_PROOF_FILE) + fn default_dkg_public_key_with_proof_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::DKG_PUBLIC_KEY_WITH_PROOF_FILE) } } @@ -334,11 +368,11 @@ impl Default for CoconutSigner { fn default() -> Self { Self { enabled: Default::default(), - dkg_persistent_state_path: CoconutSigner::default_dkg_persistent_state_path(), - verification_key_path: CoconutSigner::default_coconut_verification_key_path(), - secret_key_path: CoconutSigner::default_coconut_secret_key_path(), - decryption_key_path: CoconutSigner::default_dkg_decryption_key_path(), - public_key_with_proof_path: CoconutSigner::default_dkg_public_key_with_proof_path(), + 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, } } @@ -351,20 +385,18 @@ impl Config { pub fn with_id(mut self, id: &str) -> Self { self.base.id = id.to_string(); - self.node_status_api.database_path = - Config::default_data_directory(Some(id)).join(NodeStatusAPI::DB_FILE); + self.node_status_api.database_path = NodeStatusAPI::default_database_path(id); self.network_monitor.credentials_database_path = - Config::default_data_directory(Some(id)).join(NetworkMonitor::DB_FILE); + NetworkMonitor::default_credentials_database_path(id); self.coconut_signer.dkg_persistent_state_path = - Config::default_data_directory(Some(id)).join(CoconutSigner::DKG_PERSISTENT_STATE_FILE); - self.coconut_signer.verification_key_path = Config::default_data_directory(Some(id)) - .join(CoconutSigner::COCONUT_VERIFICATION_KEY_FILE); - self.coconut_signer.secret_key_path = - Config::default_data_directory(Some(id)).join(CoconutSigner::COCONUT_SECRET_KEY_FILE); + CoconutSigner::default_dkg_persistent_state_path(id); + self.coconut_signer.verification_key_path = + CoconutSigner::default_coconut_verification_key_path(id); + self.coconut_signer.secret_key_path = CoconutSigner::default_coconut_secret_key_path(id); self.coconut_signer.decryption_key_path = - Config::default_data_directory(Some(id)).join(CoconutSigner::DKG_DECRYPTION_KEY_FILE); - self.coconut_signer.public_key_with_proof_path = Config::default_data_directory(Some(id)) - .join(CoconutSigner::DKG_PUBLIC_KEY_WITH_PROOF_FILE); + CoconutSigner::default_dkg_decryption_key_path(id); + self.coconut_signer.public_key_with_proof_path = + CoconutSigner::default_dkg_public_key_with_proof_path(id); self } @@ -383,30 +415,33 @@ impl Config { self } - #[cfg(feature = "coconut")] pub fn with_coconut_signer_enabled(mut self, enabled: bool) -> Self { self.coconut_signer.enabled = enabled; self } - pub fn with_custom_nymd_validator(mut self, validator: Url) -> Self { + pub fn with_custom_nyxd_validator(mut self, validator: Url) -> Self { self.base.local_validator = validator; self } - #[cfg(feature = "coconut")] pub fn with_announce_address(mut self, announce_address: Url) -> Self { self.base.announce_address = announce_address; self } - pub fn with_custom_mixnet_contract<S: Into<String>>(mut self, mixnet_contract: S) -> Self { - self.base.mixnet_contract_address = mixnet_contract.into(); + pub fn with_custom_mixnet_contract(mut self, mixnet_contract: nyxd::AccountId) -> Self { + self.base.mixnet_contract_address = mixnet_contract; self } - pub fn with_mnemonic<S: Into<String>>(mut self, mnemonic: S) -> Self { - self.base.mnemonic = mnemonic.into(); + 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 } @@ -433,7 +468,6 @@ impl Config { self.network_monitor.enabled } - #[cfg(feature = "coconut")] pub fn get_coconut_signer_enabled(&self) -> bool { self.coconut_signer.enabled } @@ -446,26 +480,27 @@ impl Config { self.network_monitor.credentials_database_path.clone() } - // TODO: Remove if still unused - #[allow(dead_code)] pub fn get_rewarding_enabled(&self) -> bool { self.rewarding.enabled } - pub fn get_nymd_validator_url(&self) -> Url { + pub fn get_nyxd_url(&self) -> Url { self.base.local_validator.clone() } - #[cfg(feature = "coconut")] pub fn get_announce_address(&self) -> Url { self.base.announce_address.clone() } - pub fn get_mixnet_contract_address(&self) -> String { + pub fn get_mixnet_contract_address(&self) -> nyxd::AccountId { self.base.mixnet_contract_address.clone() } - pub fn get_mnemonic(&self) -> String { + 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() } @@ -513,40 +548,46 @@ impl Config { self.network_monitor.per_node_test_packets } - pub fn get_caching_interval(&self) -> Duration { + pub fn get_topology_caching_interval(&self) -> Duration { self.topology_cacher.caching_interval } + pub fn get_node_status_caching_interval(&self) -> Duration { + self.node_status_api.caching_interval + } + + pub fn get_circulating_supply_caching_interval(&self) -> Duration { + self.circulating_supply_cacher.caching_interval + } + + pub fn get_circulating_supply_enabled(&self) -> bool { + self.circulating_supply_cacher.enabled + } + pub fn get_node_status_api_database_path(&self) -> PathBuf { self.node_status_api.database_path.clone() } - #[cfg(feature = "coconut")] pub fn persistent_state_path(&self) -> PathBuf { self.coconut_signer.dkg_persistent_state_path.clone() } - #[cfg(feature = "coconut")] pub fn verification_key_path(&self) -> PathBuf { self.coconut_signer.verification_key_path.clone() } - #[cfg(feature = "coconut")] pub fn secret_key_path(&self) -> PathBuf { self.coconut_signer.secret_key_path.clone() } - #[cfg(feature = "coconut")] pub fn decryption_key_path(&self) -> PathBuf { self.coconut_signer.decryption_key_path.clone() } - #[cfg(feature = "coconut")] pub fn public_key_with_proof_path(&self) -> PathBuf { self.coconut_signer.public_key_with_proof_path.clone() } - #[cfg(feature = "coconut")] pub fn get_dkg_contract_polling_rate(&self) -> Duration { self.coconut_signer.dkg_contract_polling_rate } diff --git a/validator-api/src/config/template.rs b/nym-api/src/support/config/template.rs similarity index 95% rename from validator-api/src/config/template.rs rename to nym-api/src/support/config/template.rs index c2a3670e28..9676eed563 100644 --- a/validator-api/src/config/template.rs +++ b/nym-api/src/support/config/template.rs @@ -6,11 +6,11 @@ pub(crate) fn config_template() -> &'static str { # This is a TOML config file. # For more information, see https://github.com/toml-lang/toml -##### main base validator-api config options ##### +##### main base nym-api config options ##### [base] -# ID specifies the human readable ID of this particular validator-api. +# ID specifies the human readable ID of this particular nym-api. id = '{{ base.id }}' # Validator server to which the API will be getting information about the network. @@ -24,6 +24,9 @@ announce_address = '{{ base.announce_address }}' # 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 }}' diff --git a/nym-api/src/support/http/mod.rs b/nym-api/src/support/http/mod.rs new file mode 100644 index 0000000000..139b00d8e7 --- /dev/null +++ b/nym-api/src/support/http/mod.rs @@ -0,0 +1,94 @@ +// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use crate::circulating_supply_api::cache::CirculatingSupplyCache; +use crate::coconut::{self, comm::QueryCommunicationChannel, InternalSignRequest}; +use crate::node_status_api::{self, NodeStatusCache}; +use crate::nym_contract_cache::cache::NymContractCache; +use crate::support::config::Config; +use crate::support::{nyxd, storage}; +use crate::{circulating_supply_api, nym_contract_cache}; +use anyhow::Result; +use rocket::http::Method; +use rocket::{Ignite, Rocket}; +use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; +use rocket_okapi::mount_endpoints_and_merged_docs; +use rocket_okapi::swagger_ui::make_swagger_ui; + +pub(crate) mod openapi; + +pub(crate) async fn setup_rocket( + config: &Config, + mix_denom: String, + _nyxd_client: nyxd::Client, + coconut_keypair: coconut::keypair::KeyPair, +) -> anyhow::Result<Rocket<Ignite>> { + let openapi_settings = rocket_okapi::settings::OpenApiSettings::default(); + let mut rocket = rocket::build(); + + mount_endpoints_and_merged_docs! { + rocket, + "/v1".to_owned(), + openapi_settings, + "/" => (vec![], openapi::custom_openapi_spec()), + "" => circulating_supply_api::circulating_supply_routes(&openapi_settings), + "" => nym_contract_cache::nym_contract_cache_routes(&openapi_settings), + "/status" => node_status_api::node_status_routes(&openapi_settings, config.get_network_monitor_enabled()), + } + + let rocket = rocket + .mount("/swagger", make_swagger_ui(&openapi::get_docs())) + .attach(setup_cors()?) + .attach(NymContractCache::stage()) + .attach(NodeStatusCache::stage()) + .attach(CirculatingSupplyCache::stage(mix_denom.clone())); + + // This is not a very nice approach. A lazy value would be more suitable, but that's still + // a nightly feature: https://github.com/rust-lang/rust/issues/74465 + let storage = if config.get_coconut_signer_enabled() || config.get_network_monitor_enabled() { + Some(storage::NymApiStorage::init(config.get_node_status_api_database_path()).await?) + } else { + None + }; + + let rocket = if config.get_coconut_signer_enabled() { + let comm_channel = QueryCommunicationChannel::new(_nyxd_client.clone()); + rocket.attach(InternalSignRequest::stage( + _nyxd_client.clone(), + mix_denom, + coconut_keypair, + comm_channel, + storage.clone().unwrap(), + )) + } else { + rocket + }; + + // see if we should start up network monitor + let rocket = if config.get_network_monitor_enabled() { + rocket.attach(storage::NymApiStorage::stage(storage.unwrap())) + } else { + rocket + }; + + Ok(rocket.ignite().await?) +} + +fn setup_cors() -> Result<Cors> { + let allowed_origins = AllowedOrigins::all(); + + // You can also deserialize this + let cors = rocket_cors::CorsOptions { + allowed_origins, + allowed_methods: vec![Method::Post, Method::Get] + .into_iter() + .map(From::from) + .collect(), + allowed_headers: AllowedHeaders::all(), + allow_credentials: true, + ..Default::default() + } + .to_cors()?; + + Ok(cors) +} diff --git a/nym-api/src/support/http/openapi.rs b/nym-api/src/support/http/openapi.rs new file mode 100644 index 0000000000..bda05b0c7a --- /dev/null +++ b/nym-api/src/support/http/openapi.rs @@ -0,0 +1,38 @@ +use okapi::openapi3::OpenApi; +use rocket_okapi::swagger_ui::SwaggerUIConfig; + +pub fn custom_openapi_spec() -> OpenApi { + use rocket_okapi::okapi::openapi3::*; + OpenApi { + openapi: OpenApi::default_version(), + info: Info { + title: "Nym API".to_owned(), + description: None, + terms_of_service: None, + contact: None, + license: None, + version: env!("CARGO_PKG_VERSION").to_owned(), + ..Default::default() + }, + servers: get_servers(), + ..Default::default() + } +} + +fn get_servers() -> Vec<rocket_okapi::okapi::openapi3::Server> { + if std::env::var_os("CARGO").is_some() { + return vec![]; + } + vec![rocket_okapi::okapi::openapi3::Server { + url: std::env::var("OPEN_API_BASE").unwrap_or_else(|_| "/api/v1/".to_owned()), + description: Some("API".to_owned()), + ..Default::default() + }] +} + +pub(crate) fn get_docs() -> SwaggerUIConfig { + SwaggerUIConfig { + url: "../v1/openapi.json".to_owned(), + ..SwaggerUIConfig::default() + } +} diff --git a/nym-api/src/support/mod.rs b/nym-api/src/support/mod.rs new file mode 100644 index 0000000000..322c3625b3 --- /dev/null +++ b/nym-api/src/support/mod.rs @@ -0,0 +1,9 @@ +// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod caching; +pub(crate) mod cli; +pub(crate) mod config; +pub(crate) mod http; +pub(crate) mod nyxd; +pub(crate) mod storage; diff --git a/validator-api/src/nymd_client.rs b/nym-api/src/support/nyxd/mod.rs similarity index 51% rename from validator-api/src/nymd_client.rs rename to nym-api/src/support/nyxd/mod.rs index 1f126d6fdc..0686e5c9ef 100644 --- a/validator-api/src/nymd_client.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -1,125 +1,123 @@ -// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net> +// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use crate::config::Config; +use crate::coconut::error::CoconutError; use crate::epoch_operations::MixnodeToReward; -use config::defaults::{NymNetworkDetails, DEFAULT_VALIDATOR_API_PORT}; -use mixnet_contract_common::families::{Family, FamilyHead}; -use mixnet_contract_common::mixnode::MixNodeDetails; -use mixnet_contract_common::reward_params::RewardingParams; -use mixnet_contract_common::{ - CurrentIntervalResponse, ExecuteMsg, GatewayBond, IdentityKey, LayerAssignment, MixId, - RewardedSetNodeStatus, +use crate::support::config::Config; +use anyhow::Result; +use async_trait::async_trait; +use cw3::ProposalResponse; +use cw4::MemberResponse; +use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; +use nym_coconut_dkg_common::types::InitialReplacementData; +use nym_coconut_dkg_common::{ + dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}, + types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId}, + verification_key::{ContractVKShare, VerificationKeyShare}, }; +use nym_config::defaults::{ChainDetails, NymNetworkDetails, DEFAULT_NYM_API_PORT}; +use nym_contracts_common::dealings::ContractSafeBytes; +use nym_mixnet_contract_common::families::{Family, FamilyHead}; +use nym_mixnet_contract_common::mixnode::MixNodeDetails; +use nym_mixnet_contract_common::reward_params::RewardingParams; +use nym_mixnet_contract_common::{ + CurrentIntervalResponse, EpochStatus, ExecuteMsg, GatewayBond, IdentityKey, LayerAssignment, + MixId, RewardedSetNodeStatus, +}; +use nym_vesting_contract_common::AccountVestingCoins; use std::sync::Arc; use tokio::sync::RwLock; -use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; -use validator_client::nymd::{ +use validator_client::nyxd::error::NyxdError; +use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient}; +use validator_client::nyxd::{ + cosmwasm_client::types::ExecuteResult, + traits::{ + CoconutBandwidthQueryClient, DkgQueryClient, DkgSigningClient, GroupQueryClient, + MultisigQueryClient, MultisigSigningClient, + }, + Fee, +}; +use validator_client::nyxd::{ hash::{Hash, SHA256_HASH_SIZE}, - Coin, CosmWasmClient, QueryNymdClient, SigningCosmWasmClient, SigningNymdClient, - TendermintTime, + AccountId, Coin, SigningNyxdClient, TendermintTime, VestingQueryClient, }; use validator_client::ValidatorClientError; -#[cfg(feature = "coconut")] -use crate::coconut::error::CoconutError; -#[cfg(feature = "coconut")] -use async_trait::async_trait; -#[cfg(feature = "coconut")] -use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; -#[cfg(feature = "coconut")] -use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; -#[cfg(feature = "coconut")] -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; -#[cfg(feature = "coconut")] -use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; -#[cfg(feature = "coconut")] -use contracts_common::dealings::ContractSafeBytes; -#[cfg(feature = "coconut")] -use cw3::ProposalResponse; -#[cfg(feature = "coconut")] -use validator_client::nymd::{ - cosmwasm_client::types::ExecuteResult, - traits::{ - CoconutBandwidthQueryClient, DkgQueryClient, DkgSigningClient, MultisigQueryClient, - MultisigSigningClient, - }, - AccountId, Fee, -}; +pub(crate) struct Client(pub(crate) Arc<RwLock<validator_client::Client<SigningNyxdClient>>>); -pub(crate) struct Client<C>(pub(crate) Arc<RwLock<validator_client::Client<C>>>); - -impl<C> Clone for Client<C> { +impl Clone for Client { fn clone(&self) -> Self { Client(Arc::clone(&self.0)) } } -impl Client<QueryNymdClient> { - pub(crate) fn new_query(config: &Config) -> Self { +impl Client { + pub(crate) fn new(config: &Config) -> Self { // the api address is irrelevant here as **WE ARE THE API** // and we won't be talking on the socket here. - let api_url = format!("http://localhost:{}", DEFAULT_VALIDATOR_API_PORT) + let api_url = format!("http://localhost:{}", DEFAULT_NYM_API_PORT) .parse() .unwrap(); - let nymd_url = config.get_nymd_validator_url(); + let nyxd_url = config.get_nyxd_url(); let details = NymNetworkDetails::new_from_env() - .with_mixnet_contract(Some(config.get_mixnet_contract_address())); + .with_mixnet_contract(Some(config.get_mixnet_contract_address().as_ref())) + .with_vesting_contract(Some(config.get_vesting_contract_address().as_ref())); let client_config = validator_client::Config::try_from_nym_network_details(&details) .expect("failed to construct valid validator client config with the provided network") - .with_urls(nymd_url, api_url); + .with_urls(nyxd_url, api_url); - let inner = - validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"); - - Client(Arc::new(RwLock::new(inner))) - } -} - -impl Client<SigningNymdClient> { - pub(crate) fn new_signing(config: &Config) -> Self { - // the api address is irrelevant here as **WE ARE THE API** - // and we won't be talking on the socket here. - let api_url = format!("http://localhost:{}", DEFAULT_VALIDATOR_API_PORT) - .parse() - .unwrap(); - let nymd_url = config.get_nymd_validator_url(); - - let details = NymNetworkDetails::new_from_env() - .with_mixnet_contract(Some(config.get_mixnet_contract_address())); - - let client_config = validator_client::Config::try_from_nym_network_details(&details) - .expect("failed to construct valid validator client config with the provided network") - .with_urls(nymd_url, api_url); - - let mnemonic = config - .get_mnemonic() - .parse() - .expect("the mnemonic is invalid!"); + let mnemonic = config.get_mnemonic(); let inner = validator_client::Client::new_signing(client_config, mnemonic) - .expect("Failed to connect to nymd!"); + .expect("Failed to connect to nyxd!"); Client(Arc::new(RwLock::new(inner))) } -} -impl<C> Client<C> { + pub(crate) async fn client_address(&self) -> AccountId { + self.0.read().await.nyxd.address().clone() + } + + pub(crate) async fn chain_details(&self) -> ChainDetails { + self.0.read().await.nyxd.current_chain_details().clone() + } + + pub(crate) async fn get_rewarding_validator_address( + &self, + ) -> Result<AccountId, ValidatorClientError> { + let cosmwasm_addr = self + .0 + .read() + .await + .nyxd + .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 + // 2) cosmwasm accepts invalid addresses + // 3) cosmrs fails to parse valid addresses + // all of those options are BAD + cosmwasm_addr + .clone() + .parse() + .map_err(|_| NyxdError::MalformedAccountAddress(cosmwasm_addr).into()) + } + // a helper function for the future to obtain the current block timestamp #[allow(dead_code)] pub(crate) async fn current_block_timestamp( &self, - ) -> Result<TendermintTime, ValidatorClientError> - where - C: CosmWasmClient + Sync, - { + ) -> Result<TendermintTime, ValidatorClientError> { let time = self .0 .read() .await - .nymd + .nyxd .get_current_block_timestamp() .await?; @@ -136,11 +134,8 @@ impl<C> Client<C> { pub(crate) async fn get_block_hash( &self, height: u32, - ) -> Result<Option<[u8; SHA256_HASH_SIZE]>, ValidatorClientError> - where - C: CosmWasmClient + Sync, - { - let hash = match self.0.read().await.nymd.get_block_hash(height).await? { + ) -> Result<Option<[u8; SHA256_HASH_SIZE]>, ValidatorClientError> { + let hash = match self.0.read().await.nyxd.get_block_hash(height).await? { Hash::Sha256(hash) => Some(hash), Hash::None => None, }; @@ -148,75 +143,101 @@ impl<C> Client<C> { Ok(hash) } - pub(crate) async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> - where - C: CosmWasmClient + Sync + Send, - { - self.0.read().await.get_all_nymd_mixnodes_detailed().await + pub(crate) async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> { + self.0.read().await.get_all_nyxd_mixnodes_detailed().await } - pub(crate) async fn get_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> - where - C: CosmWasmClient + Sync + Send, - { - self.0.read().await.get_all_nymd_gateways().await + pub(crate) async fn get_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> { + self.0.read().await.get_all_nyxd_gateways().await } pub(crate) async fn get_current_interval( &self, - ) -> Result<CurrentIntervalResponse, ValidatorClientError> - where - C: CosmWasmClient + Sync + Send, - { + ) -> Result<CurrentIntervalResponse, ValidatorClientError> { Ok(self.0.read().await.get_current_interval_details().await?) } + pub(crate) async fn get_current_epoch_status( + &self, + ) -> Result<EpochStatus, ValidatorClientError> { + Ok(self.0.read().await.nyxd.get_current_epoch_status().await?) + } + pub(crate) async fn get_current_rewarding_parameters( &self, - ) -> Result<RewardingParams, ValidatorClientError> - where - C: CosmWasmClient + Sync + Send, - { + ) -> Result<RewardingParams, ValidatorClientError> { Ok(self.0.read().await.get_rewarding_parameters().await?) } pub(crate) async fn get_rewarded_set_mixnodes( &self, - ) -> Result<Vec<(MixId, RewardedSetNodeStatus)>, ValidatorClientError> - where - C: CosmWasmClient + Sync + Send, - { + ) -> Result<Vec<(MixId, RewardedSetNodeStatus)>, ValidatorClientError> { self.0 .read() .await - .get_all_nymd_rewarded_set_mixnodes() + .get_all_nyxd_rewarded_set_mixnodes() .await } + pub(crate) async fn get_current_vesting_account_storage_key( + &self, + ) -> Result<u32, ValidatorClientError> { + let guard = self.0.read().await; + let vesting_contract = guard.nyxd.vesting_contract_address(); + // TODO: I don't like the usage of the hardcoded value here + let res = guard + .nyxd + .query_contract_raw(vesting_contract, b"key".to_vec()) + .await?; + if res.is_empty() { + return Ok(0); + } + + Ok(serde_json::from_slice(&res).map_err(NyxdError::from)?) + } + + pub(crate) async fn get_all_vesting_coins( + &self, + ) -> Result<Vec<AccountVestingCoins>, ValidatorClientError> { + Ok(self + .0 + .read() + .await + .nyxd + .get_all_accounts_vesting_coins() + .await?) + } + #[allow(dead_code)] - pub(crate) async fn get_all_node_families(&self) -> Result<Vec<Family>, ValidatorClientError> - where - C: CosmWasmClient + Sync + Send, - { + pub(crate) async fn get_all_node_families(&self) -> Result<Vec<Family>, ValidatorClientError> { self.0.read().await.get_all_node_families().await } pub(crate) async fn get_all_family_members( &self, - ) -> Result<Vec<(IdentityKey, FamilyHead)>, ValidatorClientError> - where - C: CosmWasmClient + Sync + Send, - { + ) -> Result<Vec<(IdentityKey, FamilyHead)>, ValidatorClientError> { self.0.read().await.get_all_family_members().await } + pub(crate) async fn get_pending_events_count(&self) -> Result<u32, ValidatorClientError> { + let pending = self.0.read().await.get_number_of_pending_events().await?; + Ok(pending.epoch_events + pending.interval_events) + } + + pub(crate) async fn begin_epoch_transition(&self) -> Result<(), ValidatorClientError> { + self.0 + .write() + .await + .nyxd + .begin_epoch_transition(None) + .await?; + Ok(()) + } + pub(crate) async fn send_rewarding_messages( &self, nodes: &[MixnodeToReward], - ) -> Result<(), ValidatorClientError> - where - C: SigningCosmWasmClient + Sync + Send, - { + ) -> Result<(), ValidatorClientError> { // for some reason, compiler complains if this is explicitly inline in code ¯\_(ツ)_/¯ #[inline] #[allow(unused_variables)] @@ -243,7 +264,7 @@ impl<C> Client<C> { self.0 .write() .await - .nymd + .nyxd .execute_multiple( &contract, msgs, @@ -258,62 +279,55 @@ impl<C> Client<C> { &self, new_rewarded_set: Vec<LayerAssignment>, expected_active_set_size: u32, - ) -> Result<(), ValidatorClientError> - where - C: SigningCosmWasmClient + Sync + Send, - { + ) -> Result<(), ValidatorClientError> { self.0 .write() .await - .nymd + .nyxd .advance_current_epoch(new_rewarded_set, expected_active_set_size, None) .await?; Ok(()) } - pub(crate) async fn reconcile_epoch_events(&self) -> Result<(), ValidatorClientError> - where - C: SigningCosmWasmClient + Sync + Send, - { + pub(crate) async fn reconcile_epoch_events( + &self, + limit: Option<u32>, + ) -> Result<(), ValidatorClientError> { self.0 .write() .await - .nymd - .reconcile_epoch_events(None, None) + .nyxd + .reconcile_epoch_events(limit, None) .await?; Ok(()) } } #[async_trait] -#[cfg(feature = "coconut")] -impl<C> crate::coconut::client::Client for Client<C> -where - C: SigningCosmWasmClient + Sync + Send, -{ +impl crate::coconut::client::Client for Client { async fn address(&self) -> AccountId { - self.0.read().await.nymd.address().clone() + self.client_address().await } async fn get_tx( &self, tx_hash: &str, - ) -> crate::coconut::error::Result<validator_client::nymd::TxResponse> { + ) -> crate::coconut::error::Result<validator_client::nyxd::TxResponse> { let tx_hash = tx_hash - .parse::<validator_client::nymd::tx::Hash>() + .parse::<validator_client::nyxd::tx::Hash>() .map_err(|_| CoconutError::TxHashParseError)?; - Ok(self.0.read().await.nymd.get_tx(tx_hash).await?) + Ok(self.0.read().await.nyxd.get_tx(tx_hash).await?) } async fn get_proposal( &self, proposal_id: u64, ) -> crate::coconut::error::Result<ProposalResponse> { - Ok(self.0.read().await.nymd.get_proposal(proposal_id).await?) + Ok(self.0.read().await.nyxd.get_proposal(proposal_id).await?) } async fn list_proposals(&self) -> crate::coconut::error::Result<Vec<ProposalResponse>> { - Ok(self.0.read().await.get_all_nymd_proposals().await?) + Ok(self.0.read().await.get_all_nyxd_proposals().await?) } async fn get_spent_credential( @@ -324,27 +338,37 @@ where .0 .read() .await - .nymd + .nyxd .get_spent_credential(blinded_serial_number) .await?) } - async fn get_current_epoch_state(&self) -> crate::coconut::error::Result<EpochState> { - Ok(self.0.read().await.nymd.get_current_epoch_state().await?) + async fn get_current_epoch(&self) -> crate::coconut::error::Result<Epoch> { + Ok(self.0.read().await.nyxd.get_current_epoch().await?) + } + + async fn group_member(&self, addr: String) -> crate::coconut::error::Result<MemberResponse> { + Ok(self.0.read().await.nyxd.member(addr).await?) } async fn get_current_epoch_threshold( &self, - ) -> crate::coconut::error::Result<Option<dkg::Threshold>> { + ) -> crate::coconut::error::Result<Option<nym_dkg::Threshold>> { Ok(self .0 .read() .await - .nymd + .nyxd .get_current_epoch_threshold() .await?) } + async fn get_initial_dealers( + &self, + ) -> crate::coconut::error::Result<Option<InitialReplacementData>> { + Ok(self.0.read().await.nyxd.get_initial_dealers().await?) + } + async fn get_self_registered_dealer_details( &self, ) -> crate::coconut::error::Result<DealerDetailsResponse> { @@ -353,30 +377,31 @@ where .0 .read() .await - .nymd + .nyxd .get_dealer_details(self_address) .await?) } async fn get_current_dealers(&self) -> crate::coconut::error::Result<Vec<DealerDetails>> { - Ok(self.0.read().await.get_all_nymd_current_dealers().await?) + Ok(self.0.read().await.get_all_nyxd_current_dealers().await?) } async fn get_dealings( &self, idx: usize, ) -> crate::coconut::error::Result<Vec<ContractDealing>> { - Ok(self.0.read().await.get_all_nymd_epoch_dealings(idx).await?) + Ok(self.0.read().await.get_all_nyxd_epoch_dealings(idx).await?) } async fn get_verification_key_shares( &self, + epoch_id: EpochId, ) -> crate::coconut::error::Result<Vec<ContractVKShare>> { Ok(self .0 .read() .await - .get_all_nymd_verification_key_shares() + .get_all_nyxd_verification_key_shares(epoch_id) .await?) } @@ -389,7 +414,7 @@ where self.0 .read() .await - .nymd + .nyxd .vote_proposal(proposal_id, vote_yes, fee) .await?; Ok(()) @@ -399,49 +424,62 @@ where self.0 .read() .await - .nymd + .nyxd .execute_proposal(proposal_id, None) .await?; Ok(()) } + async fn advance_epoch_state(&self) -> crate::coconut::error::Result<()> { + self.0 + .write() + .await + .nyxd + .advance_dkg_epoch_state(None) + .await?; + Ok(()) + } + async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, announce_address: String, + resharing: bool, ) -> Result<ExecuteResult, CoconutError> { Ok(self .0 .write() .await - .nymd - .register_dealer(bte_key, announce_address, None) + .nyxd + .register_dealer(bte_key, announce_address, resharing, None) .await?) } async fn submit_dealing( &self, dealing_bytes: ContractSafeBytes, + resharing: bool, ) -> Result<ExecuteResult, CoconutError> { Ok(self .0 .write() .await - .nymd - .submit_dealing_bytes(dealing_bytes, None) + .nyxd + .submit_dealing_bytes(dealing_bytes, resharing, None) .await?) } async fn submit_verification_key_share( &self, share: VerificationKeyShare, + resharing: bool, ) -> crate::coconut::error::Result<ExecuteResult> { Ok(self .0 .write() .await - .nymd - .submit_verification_key_share(share, None) + .nyxd + .submit_verification_key_share(share, resharing, None) .await?) } } diff --git a/validator-api/src/storage/manager.rs b/nym-api/src/support/storage/manager.rs similarity index 91% rename from validator-api/src/storage/manager.rs rename to nym-api/src/support/storage/manager.rs index 6f736294de..f3e4d6e011 100644 --- a/validator-api/src/storage/manager.rs +++ b/nym-api/src/support/storage/manager.rs @@ -3,10 +3,10 @@ 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}; -use crate::storage::models::{ +use crate::support::storage::models::{ ActiveGateway, ActiveMixnode, NodeStatus, RewardingReport, TestingRoute, }; -use mixnet_contract_common::{EpochId, IdentityKey, MixId}; +use nym_mixnet_contract_common::{EpochId, IdentityKey, MixId}; use std::convert::TryFrom; #[derive(Clone)] @@ -46,7 +46,7 @@ impl AvgGatewayReliability { // all SQL goes here impl StorageManager { - pub(super) async fn get_mixnode_mix_ids_by_identity( + pub(crate) async fn get_mixnode_mix_ids_by_identity( &self, identity: &str, ) -> Result<Vec<MixId>, sqlx::Error> { @@ -63,7 +63,7 @@ impl StorageManager { Ok(ids) } - pub(super) async fn get_all_avg_mix_reliability_in_last_24hr( + pub(crate) async fn get_all_avg_mix_reliability_in_last_24hr( &self, end_ts_secs: i64, ) -> Result<Vec<AvgMixnodeReliability>, sqlx::Error> { @@ -72,7 +72,7 @@ impl StorageManager { .await } - pub(super) async fn get_all_avg_gateway_reliability_in_last_24hr( + pub(crate) async fn get_all_avg_gateway_reliability_in_last_24hr( &self, end_ts_secs: i64, ) -> Result<Vec<AvgGatewayReliability>, sqlx::Error> { @@ -81,7 +81,7 @@ impl StorageManager { .await } - pub(super) async fn get_all_avg_mix_reliability_in_time_interval( + pub(crate) async fn get_all_avg_mix_reliability_in_time_interval( &self, start_ts_secs: i64, end_ts_secs: i64, @@ -109,7 +109,7 @@ impl StorageManager { Ok(result) } - pub(super) async fn get_all_avg_gateway_reliability_in_interval( + pub(crate) async fn get_all_avg_gateway_reliability_in_interval( &self, start_ts_secs: i64, end_ts_secs: i64, @@ -142,7 +142,7 @@ impl StorageManager { /// # Arguments /// /// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode. - pub(super) async fn get_mixnode_database_id( + pub(crate) async fn get_mixnode_database_id( &self, mix_id: MixId, ) -> Result<Option<i64>, sqlx::Error> { @@ -159,7 +159,7 @@ impl StorageManager { /// # Arguments /// /// * `identity`: identity (base58-encoded public key) of the gateway. - pub(super) async fn get_gateway_id(&self, identity: &str) -> Result<Option<i64>, sqlx::Error> { + pub(crate) async fn get_gateway_id(&self, identity: &str) -> Result<Option<i64>, sqlx::Error> { let id = sqlx::query!( "SELECT id FROM gateway_details WHERE identity = ?", identity @@ -176,7 +176,7 @@ impl StorageManager { /// # Arguments /// /// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode. - pub(super) async fn get_mixnode_owner( + pub(crate) async fn get_mixnode_owner( &self, mix_id: MixId, ) -> Result<Option<String>, sqlx::Error> { @@ -193,7 +193,7 @@ impl StorageManager { /// # Arguments /// /// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode. - pub(super) async fn get_mixnode_identity_key( + pub(crate) async fn get_mixnode_identity_key( &self, mix_id: MixId, ) -> Result<Option<IdentityKey>, sqlx::Error> { @@ -213,7 +213,7 @@ impl StorageManager { /// # Arguments /// /// * `identity`: identity (base58-encoded public key) of the gateway. - pub(super) async fn get_gateway_owner( + pub(crate) async fn get_gateway_owner( &self, identity: &str, ) -> Result<Option<String>, sqlx::Error> { @@ -235,7 +235,7 @@ impl StorageManager { /// /// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode. /// * `timestamp`: unix timestamp of the lower bound of the selection. - pub(super) async fn get_mixnode_statuses_since( + pub(crate) async fn get_mixnode_statuses_since( &self, mix_id: MixId, timestamp: i64, @@ -263,7 +263,7 @@ impl StorageManager { /// /// * `identity`: identity (base58-encoded public key) of the gateway. /// * `timestamp`: unix timestamp of the lower bound of the selection. - pub(super) async fn get_gateway_statuses_since( + pub(crate) async fn get_gateway_statuses_since( &self, identity: &str, timestamp: i64, @@ -289,7 +289,7 @@ impl StorageManager { /// # Arguments /// /// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode. - pub(super) async fn get_mixnode_historical_uptimes( + pub(crate) async fn get_mixnode_historical_uptimes( &self, mix_id: MixId, ) -> Result<Vec<HistoricalUptime>, sqlx::Error> { @@ -327,7 +327,7 @@ impl StorageManager { /// # Arguments /// /// * `identity`: identity (base58-encoded public key) of the gateway. - pub(super) async fn get_gateway_historical_uptimes( + pub(crate) async fn get_gateway_historical_uptimes( &self, identity: &str, ) -> Result<Vec<HistoricalUptime>, sqlx::Error> { @@ -367,7 +367,7 @@ impl StorageManager { /// /// * `since`: unix timestamp indicating the lower bound interval of the selection. /// * `until`: unix timestamp indicating the upper bound interval of the selection. - pub(super) async fn get_mixnode_statuses_by_database_id( + pub(crate) async fn get_mixnode_statuses_by_database_id( &self, id: i64, since: i64, @@ -388,7 +388,7 @@ impl StorageManager { .await } - pub(super) async fn get_average_reliability_in_interval( + pub(crate) async fn get_mixnode_average_reliability_in_interval( &self, id: i64, start: i64, @@ -408,6 +408,26 @@ impl StorageManager { Ok(result.reliability) } + pub(super) async fn get_gateway_average_reliability_in_interval( + &self, + id: i64, + start: i64, + end: i64, + ) -> Result<Option<f32>, sqlx::Error> { + let result = sqlx::query!( + r#" + SELECT AVG(reliability) as "reliability: f32" FROM gateway_status + WHERE gateway_details_id= ? AND timestamp >= ? AND timestamp <= ? + "#, + id, + start, + end + ) + .fetch_one(&self.connection_pool) + .await?; + Ok(result.reliability) + } + /// Gets all reliability statuses for gateway with particular id that were inserted /// into the database within the specified time interval. /// @@ -415,7 +435,7 @@ impl StorageManager { /// /// * `since`: unix timestamp indicating the lower bound interval of the selection. /// * `until`: unix timestamp indicating the upper bound interval of the selection. - pub(super) async fn get_gateway_statuses_by_id( + pub(crate) async fn get_gateway_statuses_by_id( &self, id: i64, since: i64, @@ -442,7 +462,7 @@ impl StorageManager { /// /// * `timestamp`: unix timestamp indicating when the measurements took place. /// * `mixnode_results`: reliability results of each node that got tested. - pub(super) async fn submit_mixnode_statuses( + pub(crate) async fn submit_mixnode_statuses( &self, timestamp: i64, mixnode_results: Vec<MixnodeResult>, @@ -488,7 +508,7 @@ impl StorageManager { /// /// * `timestamp`: unix timestamp indicating when the measurements took place. /// * `gateway_results`: reliability results of each node that got tested. - pub(super) async fn submit_gateway_statuses( + pub(crate) async fn submit_gateway_statuses( &self, timestamp: i64, gateway_results: Vec<GatewayResult>, @@ -537,7 +557,7 @@ impl StorageManager { /// # Arguments /// /// * `testing_route`: test route used for this particular network monitor run. - pub(super) async fn submit_testing_route_used( + pub(crate) async fn submit_testing_route_used( &self, testing_route: TestingRoute, ) -> Result<(), sqlx::Error> { @@ -565,7 +585,7 @@ impl StorageManager { /// /// * `db_mixnode_id`: id (as saved in the database) of the mixnode. /// * `since`: unix timestamp indicating the lower bound interval of the selection. - pub(super) async fn get_mixnode_testing_route_presence_count_since( + pub(crate) async fn get_mixnode_testing_route_presence_count_since( &self, db_mixnode_id: i64, since: i64, @@ -604,7 +624,7 @@ impl StorageManager { /// /// * `gateway_id`: id (as saved in the database) of the gateway. /// * `since`: unix timestamp indicating the lower bound interval of the selection. - pub(super) async fn get_gateway_testing_route_presence_count_since( + pub(crate) async fn get_gateway_testing_route_presence_count_since( &self, gateway_id: i64, since: i64, @@ -636,7 +656,7 @@ impl StorageManager { } /// Checks whether there are already any historical uptimes with this particular date. - pub(super) async fn check_for_historical_uptime_existence( + pub(crate) async fn check_for_historical_uptime_existence( &self, today_iso_8601: &str, ) -> Result<bool, sqlx::Error> { @@ -656,7 +676,7 @@ impl StorageManager { /// * `node_id`: id of the mixnode (as inserted in `mixnode_details_id` table). /// * `date`: date associated with the uptime represented in ISO 8601, i.e. YYYY-MM-DD. /// * `uptime`: the actual uptime of the node during the specified day. - pub(super) async fn insert_mixnode_historical_uptime( + pub(crate) async fn insert_mixnode_historical_uptime( &self, mix_id: i64, date: &str, @@ -678,7 +698,7 @@ impl StorageManager { /// * `node_id`: id of the gateway (as inserted in `gateway_details_id` table). /// * `date`: date associated with the uptime represented in ISO 8601, i.e. YYYY-MM-DD. /// * `uptime`: the actual uptime of the node during the specified day. - pub(super) async fn insert_gateway_historical_uptime( + pub(crate) async fn insert_gateway_historical_uptime( &self, mix_id: i64, date: &str, @@ -699,7 +719,7 @@ impl StorageManager { /// # Arguments /// /// * `timestamp`: unix timestamp at which the monitor test run has occurred - pub(super) async fn insert_monitor_run(&self, timestamp: i64) -> Result<i64, sqlx::Error> { + pub(crate) async fn insert_monitor_run(&self, timestamp: i64) -> Result<i64, sqlx::Error> { let res = sqlx::query!("INSERT INTO monitor_run(timestamp) VALUES (?)", timestamp) .execute(&self.connection_pool) .await?; @@ -712,7 +732,7 @@ impl StorageManager { /// /// * `since`: unix timestamp indicating the lower bound interval of the selection. /// * `until`: unix timestamp indicating the upper bound interval of the selection. - pub(super) async fn get_monitor_runs_count( + pub(crate) async fn get_monitor_runs_count( &self, since: i64, until: i64, @@ -734,7 +754,7 @@ impl StorageManager { /// # Arguments /// /// * `until`: timestamp specifying the purge cutoff. - pub(super) async fn purge_old_mixnode_statuses( + pub(crate) async fn purge_old_mixnode_statuses( &self, timestamp: i64, ) -> Result<(), sqlx::Error> { @@ -750,7 +770,7 @@ impl StorageManager { /// # Arguments /// /// * `until`: timestamp specifying the purge cutoff. - pub(super) async fn purge_old_gateway_statuses( + pub(crate) async fn purge_old_gateway_statuses( &self, timestamp: i64, ) -> Result<(), sqlx::Error> { @@ -767,7 +787,7 @@ impl StorageManager { /// /// * `since`: indicates the lower bound timestamp for deciding whether given mixnode is active /// * `until`: indicates the upper bound timestamp for deciding whether given mixnode is active - pub(super) async fn get_all_active_mixnodes_in_interval( + pub(crate) async fn get_all_active_mixnodes_in_interval( &self, since: i64, until: i64, @@ -801,7 +821,7 @@ impl StorageManager { /// /// * `since`: indicates the lower bound timestamp for deciding whether given gateway is active /// * `until`: indicates the upper bound timestamp for deciding whether given gateway is active - pub(super) async fn get_all_active_gateways_in_interval( + pub(crate) async fn get_all_active_gateways_in_interval( &self, since: i64, until: i64, @@ -847,7 +867,7 @@ impl StorageManager { /// # Arguments /// /// * `report`: report to insert into the database - pub(super) async fn insert_rewarding_report( + pub(crate) async fn insert_rewarding_report( &self, report: RewardingReport, ) -> Result<(), sqlx::Error> { @@ -865,7 +885,7 @@ impl StorageManager { Ok(()) } - pub(super) async fn get_rewarding_report( + pub(crate) async fn get_rewarding_report( &self, absolute_epoch_id: EpochId, ) -> Result<Option<RewardingReport>, sqlx::Error> { @@ -890,7 +910,7 @@ impl StorageManager { /// /// * `since`: unix timestamp indicating the lower bound interval of the selection. /// * `until`: unix timestamp indicating the upper bound interval of the selection. - pub(super) async fn get_all_active_mixnodes_statuses_in_interval( + pub(crate) async fn get_all_active_mixnodes_statuses_in_interval( &self, since: i64, until: i64, @@ -924,7 +944,7 @@ impl StorageManager { /// /// * `since`: unix timestamp indicating the lower bound interval of the selection. /// * `until`: unix timestamp indicating the upper bound interval of the selection. - pub(super) async fn get_all_active_gateways_statuses_in_interval( + pub(crate) async fn get_all_active_gateways_statuses_in_interval( &self, since: i64, until: i64, @@ -957,8 +977,7 @@ impl StorageManager { /// /// * `tx_hash`: hash of the deposit transaction. /// * `blinded_signature_response`: the encrypted blinded signature response. - #[cfg(feature = "coconut")] - pub(super) async fn insert_blinded_signature_response( + pub(crate) async fn insert_blinded_signature_response( &self, tx_hash: &str, blinded_signature_response: &str, @@ -978,8 +997,7 @@ impl StorageManager { /// # Arguments /// /// * `tx_hash`: transaction hash of the deposit. - #[cfg(feature = "coconut")] - pub(super) async fn get_blinded_signature_response( + pub(crate) async fn get_blinded_signature_response( &self, tx_hash: &str, ) -> Result<Option<String>, sqlx::Error> { diff --git a/validator-api/src/storage/mod.rs b/nym-api/src/support/storage/mod.rs similarity index 76% rename from validator-api/src/storage/mod.rs rename to nym-api/src/support/storage/mod.rs index 21a5b2ca23..ce80251e3d 100644 --- a/validator-api/src/storage/mod.rs +++ b/nym-api/src/support/storage/mod.rs @@ -4,13 +4,13 @@ use crate::network_monitor::monitor::summary_producer::{GatewayResult, MixnodeResult}; use crate::network_monitor::test_route::TestRoute; use crate::node_status_api::models::{ - GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport, MixnodeUptimeHistory, Uptime, - ValidatorApiStorageError, + GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport, MixnodeUptimeHistory, + NymApiStorageError, Uptime, }; use crate::node_status_api::{ONE_DAY, ONE_HOUR}; use crate::storage::manager::StorageManager; use crate::storage::models::{NodeStatus, RewardingReport, TestingRoute}; -use mixnet_contract_common::{EpochId, MixId}; +use nym_mixnet_contract_common::{EpochId, MixId}; use rocket::fairing::AdHoc; use sqlx::ConnectOptions; use std::path::PathBuf; @@ -23,13 +23,13 @@ pub(crate) mod models; // note that clone here is fine as upon cloning the same underlying pool will be used #[derive(Clone)] -pub(crate) struct ValidatorApiStorage { +pub(crate) struct NymApiStorage { pub manager: StorageManager, } -impl ValidatorApiStorage { - pub async fn init(database_path: PathBuf) -> Result<Self, ValidatorApiStorageError> { - // TODO: we can inject here more stuff based on our validator-api global config +impl NymApiStorage { + pub async fn init(database_path: PathBuf) -> Result<Self, NymApiStorageError> { + // 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) @@ -41,31 +41,27 @@ impl ValidatorApiStorage { let connection_pool = match sqlx::SqlitePool::connect_with(opts).await { Ok(db) => db, - Err(e) => { - error!("Failed to connect to SQLx database: {}", e); - return Err(ValidatorApiStorageError::InternalDatabaseError( - e.to_string(), - )); + Err(err) => { + error!("Failed to connect to SQLx database: {err}"); + return Err(err.into()); } }; - if let Err(e) = sqlx::migrate!("./migrations").run(&connection_pool).await { - error!("Failed to initialize SQLx database: {}", e); - return Err(ValidatorApiStorageError::InternalDatabaseError( - e.to_string(), - )); + if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await { + error!("Failed to initialize SQLx database: {err}"); + return Err(err.into()); } info!("Database migration finished!"); - let storage = ValidatorApiStorage { + let storage = NymApiStorage { manager: StorageManager { connection_pool }, }; Ok(storage) } - pub(crate) fn stage(storage: ValidatorApiStorage) -> AdHoc { + pub(crate) fn stage(storage: NymApiStorage) -> AdHoc { AdHoc::try_on_ignite("SQLx Database", |rocket| async { Ok(rocket.manage(storage)) }) @@ -75,7 +71,7 @@ impl ValidatorApiStorage { pub(crate) async fn mix_identity_to_mix_ids( &self, identity: &str, - ) -> Result<Vec<MixId>, ValidatorApiStorageError> { + ) -> Result<Vec<MixId>, NymApiStorageError> { Ok(self .manager .get_mixnode_mix_ids_by_identity(identity) @@ -86,7 +82,7 @@ impl ValidatorApiStorage { pub(crate) async fn mix_identity_to_latest_mix_id( &self, identity: &str, - ) -> Result<Option<MixId>, ValidatorApiStorageError> { + ) -> Result<Option<MixId>, NymApiStorageError> { Ok(self .mix_identity_to_mix_ids(identity) .await? @@ -97,7 +93,7 @@ impl ValidatorApiStorage { pub(crate) async fn get_all_avg_gateway_reliability_in_last_24hr( &self, end_ts_secs: i64, - ) -> Result<Vec<AvgGatewayReliability>, ValidatorApiStorageError> { + ) -> Result<Vec<AvgGatewayReliability>, NymApiStorageError> { let result = self .manager .get_all_avg_gateway_reliability_in_last_24hr(end_ts_secs) @@ -109,7 +105,7 @@ impl ValidatorApiStorage { pub(crate) async fn get_all_avg_mix_reliability_in_last_24hr( &self, end_ts_secs: i64, - ) -> Result<Vec<AvgMixnodeReliability>, ValidatorApiStorageError> { + ) -> Result<Vec<AvgMixnodeReliability>, NymApiStorageError> { let result = self .manager .get_all_avg_mix_reliability_in_last_24hr(end_ts_secs) @@ -129,7 +125,7 @@ impl ValidatorApiStorage { &self, mix_id: MixId, since: i64, - ) -> Result<Vec<NodeStatus>, ValidatorApiStorageError> { + ) -> Result<Vec<NodeStatus>, NymApiStorageError> { let statuses = self .manager .get_mixnode_statuses_since(mix_id, since) @@ -149,7 +145,7 @@ impl ValidatorApiStorage { &self, identity: &str, since: i64, - ) -> Result<Vec<NodeStatus>, ValidatorApiStorageError> { + ) -> Result<Vec<NodeStatus>, NymApiStorageError> { let statuses = self .manager .get_gateway_statuses_since(identity, since) @@ -166,7 +162,7 @@ impl ValidatorApiStorage { pub(crate) async fn construct_mixnode_report( &self, mix_id: MixId, - ) -> Result<MixnodeStatusReport, ValidatorApiStorageError> { + ) -> Result<MixnodeStatusReport, NymApiStorageError> { let now = OffsetDateTime::now_utc(); let day_ago = (now - ONE_DAY).unix_timestamp(); let hour_ago = (now - ONE_HOUR).unix_timestamp(); @@ -175,7 +171,7 @@ impl ValidatorApiStorage { // if we have no statuses, the node doesn't exist (or monitor is down), but either way, we can't make a report if statuses.is_empty() { - return Err(ValidatorApiStorageError::MixnodeReportNotFound(mix_id)); + return Err(NymApiStorageError::MixnodeReportNotFound { mix_id }); } // determine the number of runs the mixnode should have been online for @@ -210,7 +206,7 @@ impl ValidatorApiStorage { pub(crate) async fn construct_gateway_report( &self, identity: &str, - ) -> Result<GatewayStatusReport, ValidatorApiStorageError> { + ) -> Result<GatewayStatusReport, NymApiStorageError> { let now = OffsetDateTime::now_utc(); let day_ago = (now - ONE_DAY).unix_timestamp(); let hour_ago = (now - ONE_HOUR).unix_timestamp(); @@ -219,9 +215,9 @@ impl ValidatorApiStorage { // if we have no statuses, the node doesn't exist (or monitor is down), but either way, we can't make a report if statuses.is_empty() { - return Err(ValidatorApiStorageError::GatewayReportNotFound( - identity.to_owned(), - )); + return Err(NymApiStorageError::GatewayReportNotFound { + identity: identity.to_owned(), + }); } // determine the number of runs the gateway should have been online for @@ -249,13 +245,11 @@ impl ValidatorApiStorage { pub(crate) async fn get_mixnode_uptime_history( &self, mix_id: MixId, - ) -> Result<MixnodeUptimeHistory, ValidatorApiStorageError> { + ) -> Result<MixnodeUptimeHistory, NymApiStorageError> { let history = self.manager.get_mixnode_historical_uptimes(mix_id).await?; if history.is_empty() { - return Err(ValidatorApiStorageError::MixnodeUptimeHistoryNotFound( - mix_id, - )); + return Err(NymApiStorageError::MixnodeUptimeHistoryNotFound { mix_id }); } let mixnode_owner = @@ -279,16 +273,16 @@ impl ValidatorApiStorage { pub(crate) async fn get_gateway_uptime_history( &self, identity: &str, - ) -> Result<GatewayUptimeHistory, ValidatorApiStorageError> { + ) -> Result<GatewayUptimeHistory, NymApiStorageError> { let history = self .manager .get_gateway_historical_uptimes(identity) .await?; if history.is_empty() { - return Err(ValidatorApiStorageError::GatewayUptimeHistoryNotFound( - identity.to_owned(), - )); + return Err(NymApiStorageError::GatewayUptimeHistoryNotFound { + identity: identity.to_owned(), + }); } let gateway_owner = @@ -307,12 +301,22 @@ impl ValidatorApiStorage { &self, mix_id: MixId, end_ts_secs: i64, - ) -> Result<Uptime, ValidatorApiStorageError> { + ) -> Result<Uptime, NymApiStorageError> { let start = end_ts_secs - 86400; self.get_average_mixnode_uptime_in_time_interval(mix_id, start, end_ts_secs) .await } + pub(crate) async fn get_average_gateway_uptime_in_the_last_24hrs( + &self, + identity: &str, + end_ts_secs: i64, + ) -> Result<Uptime, NymApiStorageError> { + let start = end_ts_secs - 86400; + self.get_average_gateway_uptime_in_time_interval(identity, start, end_ts_secs) + .await + } + /// Based on the data available in the validator API, determines the average uptime of particular /// mixnode during the specified time interval. /// @@ -326,7 +330,7 @@ impl ValidatorApiStorage { mix_id: MixId, start: i64, end: i64, - ) -> Result<Uptime, ValidatorApiStorageError> { + ) -> Result<Uptime, NymApiStorageError> { let mixnode_database_id = match self.manager.get_mixnode_database_id(mix_id).await? { Some(id) => id, None => return Ok(Uptime::zero()), @@ -334,7 +338,38 @@ impl ValidatorApiStorage { let reliability = self .manager - .get_average_reliability_in_interval(mixnode_database_id, start, end) + .get_mixnode_average_reliability_in_interval(mixnode_database_id, start, end) + .await?; + + if let Some(reliability) = reliability { + Ok(Uptime::new(reliability)) + } else { + Ok(Uptime::zero()) + } + } + + /// Based on the data available in the validator API, determines the average uptime of particular + /// gateway during the specified time interval. + /// + /// # Arguments + /// + /// * `identity`: base58-encoded identity of the gateway. + /// * `since`: unix timestamp indicating the lower bound interval of the selection. + /// * `end`: unix timestamp indicating the upper bound interval of the selection. + pub(crate) async fn get_average_gateway_uptime_in_time_interval( + &self, + identity: &str, + start: i64, + end: i64, + ) -> Result<Uptime, NymApiStorageError> { + let gateway_database_id = match self.manager.get_gateway_id(identity).await? { + Some(id) => id, + None => return Ok(Uptime::zero()), + }; + + let reliability = self + .manager + .get_gateway_average_reliability_in_interval(gateway_database_id, start, end) .await?; if let Some(reliability) = reliability { @@ -356,7 +391,7 @@ impl ValidatorApiStorage { &self, start: i64, end: i64, - ) -> Result<Vec<MixnodeStatusReport>, ValidatorApiStorageError> { + ) -> Result<Vec<MixnodeStatusReport>, NymApiStorageError> { if (end - start) as u64 != ONE_DAY.as_secs() { warn!("Our current interval length breaks the 24h length assumption") } @@ -400,7 +435,7 @@ impl ValidatorApiStorage { &self, start: i64, end: i64, - ) -> Result<Vec<GatewayStatusReport>, ValidatorApiStorageError> { + ) -> Result<Vec<GatewayStatusReport>, NymApiStorageError> { if (end - start) as u64 != ONE_DAY.as_secs() { warn!("Our current interval length breaks the 24h length assumption") } @@ -441,36 +476,42 @@ impl ValidatorApiStorage { &self, monitor_run_db_id: i64, test_route: TestRoute, - ) -> Result<(), ValidatorApiStorageError> { + ) -> Result<(), NymApiStorageError> { // we MUST have those entries in the database, otherwise the route wouldn't have been chosen // in the first place let layer1_mix_db_id = self .manager .get_mixnode_database_id(test_route.layer_one_mix().mix_id) - .await - .map_err(|_| ValidatorApiStorageError::InternalDatabaseError("".to_string()))? - .ok_or_else(|| ValidatorApiStorageError::InternalDatabaseError("".to_string()))?; + .await? + .ok_or_else(|| NymApiStorageError::DatabaseInconsistency { + reason: format!("could not get db id for layer1 mixnode from network monitor run {monitor_run_db_id}"), + })?; let layer2_mix_db_id = self .manager .get_mixnode_database_id(test_route.layer_two_mix().mix_id) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))? - .ok_or_else(|| ValidatorApiStorageError::InternalDatabaseError("".to_string()))?; + .await? + .ok_or_else(|| NymApiStorageError::DatabaseInconsistency { + reason: format!("could not get db id for layer2 mixnode from network monitor run {monitor_run_db_id}"), + })?; let layer3_mix_db_id = self .manager .get_mixnode_database_id(test_route.layer_three_mix().mix_id) - .await - .map_err(|_| ValidatorApiStorageError::InternalDatabaseError("".to_string()))? - .ok_or_else(|| ValidatorApiStorageError::InternalDatabaseError("".to_string()))?; + .await? + .ok_or_else(|| NymApiStorageError::DatabaseInconsistency { + reason: format!("could not get db id for layer3 mixnode from network monitor run {monitor_run_db_id}"), + })?; let gateway_db_id = self .manager .get_gateway_id(&test_route.gateway().identity_key.to_base58_string()) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))? - .ok_or_else(|| ValidatorApiStorageError::InternalDatabaseError("".to_string()))?; + .await? + .ok_or_else(|| NymApiStorageError::DatabaseInconsistency { + reason: format!( + "could not get db id for gateway from network monitor run {monitor_run_db_id}" + ), + })?; self.manager .submit_testing_route_used(TestingRoute { @@ -480,8 +521,7 @@ impl ValidatorApiStorage { layer3_mix_db_id, monitor_run_db_id, }) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + .await?; Ok(()) } @@ -497,12 +537,8 @@ impl ValidatorApiStorage { &self, mix_id: MixId, since: Option<i64>, - ) -> Result<i32, ValidatorApiStorageError> { - let db_id = self - .manager - .get_mixnode_database_id(mix_id) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + ) -> Result<i32, NymApiStorageError> { + let db_id = self.manager.get_mixnode_database_id(mix_id).await?; if let Some(node_id) = db_id { let since = since @@ -511,7 +547,7 @@ impl ValidatorApiStorage { self.manager .get_mixnode_testing_route_presence_count_since(node_id, since) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } else { Ok(0) } @@ -529,12 +565,8 @@ impl ValidatorApiStorage { &self, identity: &str, since: Option<i64>, - ) -> Result<i32, ValidatorApiStorageError> { - let node_id = self - .manager - .get_gateway_id(identity) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + ) -> Result<i32, NymApiStorageError> { + let node_id = self.manager.get_gateway_id(identity).await?; if let Some(node_id) = node_id { let since = since @@ -543,7 +575,7 @@ impl ValidatorApiStorage { self.manager .get_gateway_testing_route_presence_count_since(node_id, since) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } else { Ok(0) } @@ -562,26 +594,20 @@ impl ValidatorApiStorage { mixnode_results: Vec<MixnodeResult>, gateway_results: Vec<GatewayResult>, test_routes: Vec<TestRoute>, - ) -> Result<(), ValidatorApiStorageError> { + ) -> Result<(), NymApiStorageError> { info!("Submitting new node results to the database. There are {} mixnode results and {} gateway results", mixnode_results.len(), gateway_results.len()); let now = OffsetDateTime::now_utc().unix_timestamp(); - let monitor_run_id = self - .manager - .insert_monitor_run(now) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + let monitor_run_id = self.manager.insert_monitor_run(now).await?; self.manager .submit_mixnode_statuses(now, mixnode_results) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + .await?; self.manager .submit_gateway_statuses(now, gateway_results) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + .await?; for test_route in test_routes { self.insert_test_route(monitor_run_id, test_route).await?; @@ -600,14 +626,14 @@ impl ValidatorApiStorage { &self, since: i64, until: i64, - ) -> Result<usize, ValidatorApiStorageError> { + ) -> Result<usize, NymApiStorageError> { let run_count = self.manager.get_monitor_runs_count(since, until).await?; if run_count < 0 { // I don't think it's ever possible for SQL to return a negative value from COUNT? - return Err(ValidatorApiStorageError::InternalDatabaseError( - "Negative run count".to_string(), - )); + return Err(NymApiStorageError::DatabaseInconsistency { + reason: "Negative run count".to_string(), + }); } Ok(run_count as usize) } @@ -625,16 +651,11 @@ impl ValidatorApiStorage { today_iso_8601: &str, mixnode_reports: &[MixnodeStatusReport], gateway_reports: &[GatewayStatusReport], - ) -> Result<(), ValidatorApiStorageError> { + ) -> Result<(), NymApiStorageError> { for report in mixnode_reports { // if this ever fails, we have a super weird error because we just constructed report for that node // and we never delete node data! - let node_id = match self - .manager - .get_mixnode_database_id(report.mix_id) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))? - { + let node_id = match self.manager.get_mixnode_database_id(report.mix_id).await? { Some(node_id) => node_id, None => { error!( @@ -647,19 +668,13 @@ impl ValidatorApiStorage { self.manager .insert_mixnode_historical_uptime(node_id, today_iso_8601, report.last_day.u8()) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + .await?; } for report in gateway_reports { // if this ever fails, we have a super weird error because we just constructed report for that node // and we never delete node data! - let node_id = match self - .manager - .get_gateway_id(&report.identity) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))? - { + let node_id = match self.manager.get_gateway_id(&report.identity).await? { Some(node_id) => node_id, None => { error!( @@ -672,8 +687,7 @@ impl ValidatorApiStorage { self.manager .insert_gateway_historical_uptime(node_id, today_iso_8601, report.last_day.u8()) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + .await?; } Ok(()) @@ -682,11 +696,11 @@ impl ValidatorApiStorage { pub(crate) async fn check_if_historical_uptimes_exist_for_date( &self, date_iso_8601: &str, - ) -> Result<bool, ValidatorApiStorageError> { + ) -> Result<bool, NymApiStorageError> { self.manager .check_for_historical_uptime_existence(date_iso_8601) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } /// Removes all ipv4 and ipv6 statuses for all mixnodes and gateways that are older than the @@ -695,60 +709,52 @@ impl ValidatorApiStorage { /// # Arguments /// /// * `until`: timestamp specifying the purge cutoff. - pub(crate) async fn purge_old_statuses( - &self, - until: i64, - ) -> Result<(), ValidatorApiStorageError> { - self.manager - .purge_old_mixnode_statuses(until) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + pub(crate) async fn purge_old_statuses(&self, until: i64) -> Result<(), NymApiStorageError> { + self.manager.purge_old_mixnode_statuses(until).await?; self.manager .purge_old_gateway_statuses(until) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } pub(crate) async fn insert_rewarding_report( &self, report: RewardingReport, - ) -> Result<(), ValidatorApiStorageError> { + ) -> Result<(), NymApiStorageError> { self.manager .insert_rewarding_report(report) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } pub(crate) async fn get_rewarding_report( &self, absolute_epoch_id: EpochId, - ) -> Result<Option<RewardingReport>, ValidatorApiStorageError> { + ) -> Result<Option<RewardingReport>, NymApiStorageError> { self.manager .get_rewarding_report(absolute_epoch_id) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } - #[cfg(feature = "coconut")] pub(crate) async fn get_blinded_signature_response( &self, tx_hash: &str, - ) -> Result<Option<String>, ValidatorApiStorageError> { + ) -> Result<Option<String>, NymApiStorageError> { self.manager .get_blinded_signature_response(tx_hash) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } - #[cfg(feature = "coconut")] pub(crate) async fn insert_blinded_signature_response( &self, tx_hash: &str, blinded_signature_response: &str, - ) -> Result<(), ValidatorApiStorageError> { + ) -> Result<(), NymApiStorageError> { self.manager .insert_blinded_signature_response(tx_hash, blinded_signature_response) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } } diff --git a/validator-api/src/storage/models.rs b/nym-api/src/support/storage/models.rs similarity index 97% rename from validator-api/src/storage/models.rs rename to nym-api/src/support/storage/models.rs index 00fd0a8953..607629a46c 100644 --- a/validator-api/src/storage/models.rs +++ b/nym-api/src/support/storage/models.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // SPDX-License-Identifier: Apache-2.0 -use mixnet_contract_common::MixId; +use nym_mixnet_contract_common::MixId; // Internally used struct to catch results from the database to calculate uptimes for given mixnode/gateway pub(crate) struct NodeStatus { diff --git a/nym-api/tests/.eslintignore b/nym-api/tests/.eslintignore new file mode 100644 index 0000000000..3b3d175248 --- /dev/null +++ b/nym-api/tests/.eslintignore @@ -0,0 +1,7 @@ +.vscode +node_modules +build +coverage +dist +.cache +jest.config.js \ No newline at end of file diff --git a/nym-api/tests/.eslintrc.json b/nym-api/tests/.eslintrc.json new file mode 100644 index 0000000000..a57c206630 --- /dev/null +++ b/nym-api/tests/.eslintrc.json @@ -0,0 +1,32 @@ +{ + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "import/extensions": "off", + "no-console": ["warn", { "allow": ["warn", "error"] }], + "import/prefer-default-export": "off", + "prettier/prettier": ["error"] + }, + "overrides": [ + { + "files": ["**/*.ts", "**/*.tsx"], + "rules": { + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off", + "no-shadow": "off", + "@typescript-eslint/no-shadow": ["error"] + } + } + ] + } + \ No newline at end of file diff --git a/nym-api/tests/.gitignore b/nym-api/tests/.gitignore new file mode 100644 index 0000000000..859116926c --- /dev/null +++ b/nym-api/tests/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +coverage/ +.DS_Store +.idea/ +junit.xml \ No newline at end of file diff --git a/validator-api/tests/README.md b/nym-api/tests/README.md similarity index 96% rename from validator-api/tests/README.md rename to nym-api/tests/README.md index 914e55bdcd..baf72a79aa 100644 --- a/validator-api/tests/README.md +++ b/nym-api/tests/README.md @@ -1,5 +1,5 @@ <!-- ABOUT THE PROJECT --> -## validator-api-test suite +## nym-api-test suite A Typescript test framework utilising Jest and Node to perform tests against the NYM validator-apis. diff --git a/nym-api/tests/functional_test/circulating_supply/circulating-supply.test.ts b/nym-api/tests/functional_test/circulating_supply/circulating-supply.test.ts new file mode 100644 index 0000000000..f3362574bf --- /dev/null +++ b/nym-api/tests/functional_test/circulating_supply/circulating-supply.test.ts @@ -0,0 +1,32 @@ +import ConfigHandler from "../../src/config/configHandler"; +import ContractCache from "../../src/endpoints/CirculatingSupply"; +let contract: ContractCache; +let config: ConfigHandler; + +describe("Get circulating supply", (): void => { + beforeAll(async (): Promise<void> => { + contract = new ContractCache(); + config = ConfigHandler.getInstance(); + }); + + it("Get circulating supply amounts", async (): Promise<void> => { + const response = await contract.getCirculatingSupply(); + const initial: number = +response.total_supply.amount; + const mixmining: number = +response.mixmining_reserve.amount; + const vest: number = +response.vesting_tokens.amount; + const circsupply: number = +response.circulating_supply.amount; + + expect(typeof response.vesting_tokens.amount).toBe("string"); + expect(initial - mixmining - vest).toStrictEqual(circsupply); + }); + + it("Get total supply value", async (): Promise<void> => { + const response = await contract.getTotalSupplyValue(); + expect(response).toStrictEqual(1000000000); + }); + + it("Get circulating supply value", async (): Promise<void> => { + const response = await contract.getCirculatingSupplyValue(); + expect(typeof response).toBe("number"); + }); +}); diff --git a/nym-api/tests/functional_test/contract_cache/contract-cache-epochs.test.ts b/nym-api/tests/functional_test/contract_cache/contract-cache-epochs.test.ts new file mode 100644 index 0000000000..482817f669 --- /dev/null +++ b/nym-api/tests/functional_test/contract_cache/contract-cache-epochs.test.ts @@ -0,0 +1,37 @@ +import ContractCache from "../../src/endpoints/ContractCache"; +import ConfigHandler from "../../src/config/configHandler"; + +let contract: ContractCache; +let config: ConfigHandler; + +describe("Get epoch info", (): void => { + beforeAll(async (): Promise<void> => { + contract = new ContractCache(); + config = ConfigHandler.getInstance(); + }); + + it("Get epoch reward params", async (): Promise<void> => { + const response = await contract.getEpochRewardParams(); + expect(typeof response.interval.reward_pool).toBe("string"); + expect(typeof response.interval.staking_supply_scale_factor).toBe("string"); + expect(typeof response.interval.staking_supply).toBe("string"); + expect(typeof response.interval.epoch_reward_budget).toBe("string"); + expect(typeof response.interval.stake_saturation_point).toBe("string"); + expect(typeof response.interval.sybil_resistance).toBe("string"); + expect(typeof response.interval.active_set_work_factor).toBe("string"); + expect(typeof response.interval.interval_pool_emission).toBe("string"); + expect(typeof response.active_set_size).toBe("number"); + expect(typeof response.rewarded_set_size).toBe("number"); + }); + + it("Get current epoch", async (): Promise<void> => { + const response = await contract.getCurrentEpoch(); + expect(typeof response.id).toBe("number"); + expect(typeof response.epochs_in_interval).toBe("number"); + expect(typeof response.current_epoch_id).toBe("number"); + expect(typeof response.current_epoch_start).toBe("string"); + expect(typeof response.epoch_length.secs).toBe("number"); + expect(typeof response.epoch_length.nanos).toBe("number"); + expect(typeof response.total_elapsed_epochs).toBe("number"); + }); +}); diff --git a/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts b/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts new file mode 100644 index 0000000000..465b6a9a12 --- /dev/null +++ b/nym-api/tests/functional_test/contract_cache/gateway/contract-cache-gateway.test.ts @@ -0,0 +1,47 @@ +import ContractCache from "../../../src/endpoints/ContractCache"; +import ConfigHandler from "../../../src/config/configHandler"; + +let contract: ContractCache; +let config: ConfigHandler; + +describe("Get gateway data", (): void => { + beforeAll(async (): Promise<void> => { + contract = new ContractCache(); + config = ConfigHandler.getInstance(); + }); + + it("Get all gateways", async (): Promise<void> => { + const response = await contract.getGateways(); + response.forEach((gateway) => { + //overview + expect(typeof gateway.owner).toBe("string"); + expect(typeof gateway.block_height).toBe("number"); + + if (gateway.proxy === null) { + return true; + } else { + expect(typeof gateway.proxy).toBe("string"); + } + + //pledge_amount + expect(typeof gateway.pledge_amount.denom).toBe("string"); + expect(typeof gateway.pledge_amount.amount).toBe("string"); + + //gateway + expect(typeof gateway.gateway.host).toBe("string"); + expect(typeof gateway.gateway.mix_port).toBe("number"); + expect(typeof gateway.gateway.clients_port).toBe("number"); + expect(typeof gateway.gateway.location).toBe("string"); + expect(typeof gateway.gateway.sphinx_key).toBe("string"); + expect(typeof gateway.gateway.identity_key).toBe("string"); + expect(typeof gateway.gateway.version).toBe("string"); + }); + }); + + it("Get blacklisted gateways", async (): Promise<void> => { + const response = await contract.getBlacklistedGateways(); + response.forEach(function (value) { + expect(typeof value).toBe("string"); + }); + }); +}); diff --git a/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts b/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts new file mode 100644 index 0000000000..31fb8d959d --- /dev/null +++ b/nym-api/tests/functional_test/contract_cache/mixnode/contract-cache-mixnode.test.ts @@ -0,0 +1,163 @@ +import ContractCache from "../../../src/endpoints/ContractCache"; +import ConfigHandler from "../../../src/config/configHandler"; + +let contract: ContractCache; +let config: ConfigHandler; + +describe("Get mixnode data", (): void => { + beforeAll(async (): Promise<void> => { + contract = new ContractCache(); + config = ConfigHandler.getInstance(); + }); + + it("Get all mixnodes", async (): Promise<void> => { + const response = await contract.getMixnodes(); + response.forEach((mixnode) => { + //bond information overview + expect(typeof mixnode.bond_information.mix_id).toBe("number"); + expect(typeof mixnode.bond_information.owner).toBe("string"); + expect(typeof mixnode.bond_information.original_pledge.amount).toBe("string"); + expect(typeof mixnode.bond_information.original_pledge.denom).toBe("string"); + expect(typeof mixnode.bond_information.layer).toBe("number"); + expect(typeof mixnode.bond_information.bonding_height).toBe("number"); + expect(typeof mixnode.bond_information.is_unbonding).toBe("boolean"); + + if (mixnode.bond_information.proxy === null) { + return true; + } else { + expect(typeof mixnode.bond_information.proxy).toBe("string"); + } + + //mixnode + expect(typeof mixnode.bond_information.mix_node.host).toBe("string"); + expect(mixnode.bond_information.mix_node.http_api_port).toStrictEqual(8000); + expect(typeof mixnode.bond_information.mix_node.verloc_port).toBe("number"); + expect(typeof mixnode.bond_information.mix_node.mix_port).toBe("number"); + expect(mixnode.bond_information.mix_node.mix_port).toStrictEqual(1789); + expect(typeof mixnode.bond_information.mix_node.verloc_port).toBe("number"); + + const identitykey = mixnode.bond_information.mix_node.identity_key; + if (typeof identitykey === "string") { + if (identitykey.length === 43) { + return true; + } else expect(identitykey).toHaveLength(44); + } + + const sphinx = mixnode.bond_information.mix_node.sphinx_key; + if (typeof sphinx === "string") { + if (sphinx.length === 43) { + return true; + } else expect(sphinx).toHaveLength(44); + } + + //rewarding details + expect(typeof mixnode.rewarding_details.cost_params.profit_margin_percent).toBe("string"); + expect(typeof mixnode.rewarding_details.cost_params.interval_operating_cost.denom).toBe("string"); + expect(typeof mixnode.rewarding_details.cost_params.interval_operating_cost.amount).toBe("string"); + expect(typeof mixnode.rewarding_details.operator).toBe("string"); + expect(typeof mixnode.rewarding_details.delegates).toBe("string"); + expect(typeof mixnode.rewarding_details.total_unit_reward).toBe("string"); + expect(typeof mixnode.rewarding_details.unit_delegation).toBe("string"); + expect(typeof mixnode.rewarding_details.last_rewarded_epoch).toBe("number"); + expect(typeof mixnode.rewarding_details.unique_delegations).toBe("number"); + }); + }); + + it("Get all mixnodes detailed", async (): Promise<void> => { + const response = await contract.getMixnodesDetailed(); + response.forEach((mixnode) => { + // overview details + expect(typeof mixnode.estimated_delegators_apy).toBe("string"); + expect(typeof mixnode.estimated_operator_apy).toBe("string"); + expect(typeof mixnode.performance).toBe("string"); + expect(typeof mixnode.uncapped_stake_saturation).toBe("string"); + expect(typeof mixnode.stake_saturation).toBe("string"); + // TODO why family is tempermental + // expect(typeof mixnode.family).toBe("string"); + + //mixnode details bond info + expect(typeof mixnode.mixnode_details.bond_information.mix_id).toBe("number") + expect(typeof mixnode.mixnode_details.bond_information.owner).toBe("string"); + expect(typeof mixnode.mixnode_details.bond_information.original_pledge.amount).toBe("string"); + expect(typeof mixnode.mixnode_details.bond_information.original_pledge.denom).toBe("string"); + expect(typeof mixnode.mixnode_details.bond_information.layer).toBe("number"); + expect(typeof mixnode.mixnode_details.bond_information.bonding_height).toBe("number"); + expect(typeof mixnode.mixnode_details.bond_information.is_unbonding).toBe("boolean"); + + if (mixnode.mixnode_details.bond_information.proxy === null) { + return true; + } else { + expect(typeof mixnode.mixnode_details.bond_information.proxy).toBe("string"); + } + + //mixnode + expect(typeof mixnode.mixnode_details.bond_information.mix_node.host).toBe("string"); + expect(mixnode.mixnode_details.bond_information.mix_node.http_api_port).toStrictEqual(8000); + expect(typeof mixnode.mixnode_details.bond_information.mix_node.verloc_port).toBe("number"); + expect(typeof mixnode.mixnode_details.bond_information.mix_node.mix_port).toBe("number"); + expect(mixnode.mixnode_details.bond_information.mix_node.mix_port).toStrictEqual(1789); + expect(typeof mixnode.mixnode_details.bond_information.mix_node.verloc_port).toBe("number"); + + const identitykey2 = mixnode.mixnode_details.bond_information.mix_node.identity_key + if (typeof identitykey2 === "string") { + if (identitykey2.length === 43) { + return true; + } else expect(identitykey2).toHaveLength(44); + } + + const sphinx2 = mixnode.mixnode_details.bond_information.mix_node.sphinx_key + if (typeof sphinx2 === "string") { + if (sphinx2.length === 43) { + return true; + } else expect(sphinx2).toHaveLength(44); + } + + //mixnode rewarding info + expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.profit_margin_percent).toBe("string"); + expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.interval_operating_cost.denom).toBe("string"); + expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.interval_operating_cost.amount).toBe("string"); + expect(typeof mixnode.mixnode_details.rewarding_details.operator).toBe("string"); + expect(typeof mixnode.mixnode_details.rewarding_details.delegates).toBe("string"); + expect(typeof mixnode.mixnode_details.rewarding_details.total_unit_reward).toBe("string"); + expect(typeof mixnode.mixnode_details.rewarding_details.unit_delegation).toBe("string"); + expect(typeof mixnode.mixnode_details.rewarding_details.last_rewarded_epoch).toBe("number"); + expect(typeof mixnode.mixnode_details.rewarding_details.unique_delegations).toBe("number"); + }); + }); + + it("Get active mixnodes", async (): Promise<void> => { + const response = await contract.getActiveMixnodes(); + response.forEach(function (mixnode) { + expect(mixnode.rewarding_details.cost_params.profit_margin_percent).toBeTruthy(); + expect(typeof mixnode.bond_information.layer).toBe("number"); + }); + }); + + it("Get active mixnodes detailed", async (): Promise<void> => { + const response = await contract.getActiveMixnodesDetailed(); + response.forEach(function (mixnode) { + expect(mixnode.mixnode_details.rewarding_details.cost_params.profit_margin_percent).toBeTruthy(); + }); + }); + + it("Get rewarded mixnodes", async (): Promise<void> => { + const response = await contract.getRewardedMixnodes(); + response.forEach(function (mixnode) { + expect(mixnode.rewarding_details.last_rewarded_epoch).toBeTruthy(); + }); + }); + + it("Get rewarded mixnodes detailed", async (): Promise<void> => { + const response = await contract.getRewardedMixnodesDetailed(); + response.forEach(function (mixnode) { + expect(mixnode.mixnode_details.rewarding_details.last_rewarded_epoch).toBeTruthy(); + }); + }); + + it("Get blacklisted mixnodes", async (): Promise<void> => { + const response = await contract.getBlacklistedMixnodes(); + response.forEach(function (value) { + expect(typeof value).toBe("number"); + }); + }); +}); diff --git a/nym-api/tests/functional_test/status/status-gateway.test.ts b/nym-api/tests/functional_test/status/status-gateway.test.ts new file mode 100644 index 0000000000..ead1da8278 --- /dev/null +++ b/nym-api/tests/functional_test/status/status-gateway.test.ts @@ -0,0 +1,54 @@ +import Status from "../../src/endpoints/Status"; +import ConfigHandler from "../../src/config/configHandler"; + +let status: Status; +let config: ConfigHandler; + +describe("Get gateway data", (): void => { + beforeAll(async (): Promise<void> => { + status = new Status(); + config = ConfigHandler.getInstance(); + }); + + it("Get all gateways detailed", async (): Promise<void> => { + const response = await status.getDetailedGateways(); + response.forEach((x) => { + expect(typeof x.gateway_bond.owner).toBe("string"); + expect(typeof x.performance).toBe("string"); + expect(typeof x.node_performance.last_24h).toBe("string"); + }); + }); + + it("Get a gateway history", async (): Promise<void> => { + const identity_key = config.environmnetConfig.gateway_identity; + const response = await status.getGatewayHistory(identity_key); + + if ("identity" in response) { + response.history.forEach((x) => { + expect(typeof x.date).toBe("string"); + expect(typeof x.uptime).toBe("number"); + }); + expect(identity_key).toStrictEqual(response.identity); + expect(typeof response.owner).toBe("string"); + } else if ("message" in response) { + expect(response.message).toContain("could not find uptime history associated with gateway"); + } + }); + + it("Get gateway core status count", async (): Promise<void> => { + const identity_key = config.environmnetConfig.gateway_identity; + const response = await status.getGatewayCoreCount(identity_key); + expect(identity_key).toStrictEqual(response.identity); + expect(typeof response.count).toBe("number"); + }); + + it("Get a gateway status report", async (): Promise<void> => { + const identity_key = config.environmnetConfig.gateway_identity; + const response = await status.getGatewayStatusReport(identity_key); + expect(identity_key).toStrictEqual(response.identity); + expect(typeof response.owner).toBe("string"); + expect(typeof response.most_recent).toBe("number"); + expect(typeof response.last_hour).toBe("number"); + expect(typeof response.last_day).toBe("number"); + }); +}); diff --git a/nym-api/tests/functional_test/status/status-mixnode.test.ts b/nym-api/tests/functional_test/status/status-mixnode.test.ts new file mode 100644 index 0000000000..8f8306e740 --- /dev/null +++ b/nym-api/tests/functional_test/status/status-mixnode.test.ts @@ -0,0 +1,119 @@ +import Status from "../../src/endpoints/Status"; +import ConfigHandler from "../../src/config/configHandler"; + +let status: Status; +let config: ConfigHandler; + +describe("Get mixnode data", (): void => { + beforeAll(async (): Promise<void> => { + status = new Status(); + config = ConfigHandler.getInstance(); + }); + + it("Get a mixnode report", async (): Promise<void> => { + const identity_key = config.environmnetConfig.mix_id; + const response = await status.getMixnodeStatusReport(identity_key); + + expect(typeof response.last_day).toBe("number"); + expect(typeof response.owner).toBe("string"); + }); + + it("Get a mixnode stake saturation", async (): Promise<void> => { + const identity_key = config.environmnetConfig.mix_id; + const response = await status.getMixnodeStakeSaturation(identity_key); + + expect(typeof response.as_at).toBe("number"); + expect(typeof response.saturation).toBe("string"); + expect(typeof response.uncapped_saturation).toBe("string"); + }); + + it("Get a mixnode average uptime", async (): Promise<void> => { + const identity_key = config.environmnetConfig.mix_id; + const response = await status.getMixnodeAverageUptime(identity_key); + + expect(identity_key).toStrictEqual(response.mix_id); + expect(typeof response.avg_uptime).toBe("number"); + }); + + it("Get a mixnode history", async (): Promise<void> => { + const identity_key = config.environmnetConfig.mix_id; + const response = await status.getMixnodeHistory(identity_key); + + if ("mix_id" in response) { + response.history.forEach((x) => { + expect(typeof x.date).toBe("string"); + expect(typeof x.uptime).toBe("number"); + }); + expect(identity_key).toStrictEqual(response.mix_id); + expect(typeof response.owner).toBe("string"); + } else if ("message" in response) { + expect(response.message).toContain("could not find uptime history associated with mixnode"); + } + }); + + it("Get a mixnode core count", async (): Promise<void> => { + const identity_key = config.environmnetConfig.mix_id; + const response = await status.getMixnodeCoreCount(identity_key); + + expect(identity_key).toStrictEqual(response.mix_id); + expect(typeof response.count).toBe("number"); + }); + + it("Get a mixnode status", async (): Promise<void> => { + const identity_key = config.environmnetConfig.mix_id; + const response = await status.getMixnodeStatus(identity_key); + + expect(response.status).toStrictEqual("active"); + }); + + it("Get a mixnode reward estimation", async (): Promise<void> => { + const identity_key = config.environmnetConfig.mix_id; + const response = await status.getMixnodeRewardComputation(identity_key); + + expect(response.reward_params.interval.sybil_resistance).toStrictEqual("0.3"); + expect(response.reward_params.active_set_size).toStrictEqual(240); + expect(typeof response.reward_params.interval.reward_pool).toBe("string"); + }); + + it("Get a mixnode inclusion probability", async (): Promise<void> => { + const identity_key = config.environmnetConfig.mix_id; + const response = await status.getMixnodeInclusionProbability(identity_key); + + expect(typeof response.in_active).toBe("string"); + }); + + it("Get all mixnodes inclusion probabilities", async (): Promise<void> => { + const response = await status.getAllMixnodeInclusionProbability(); + expect(typeof response.inclusion_probabilities[0].mix_id).toBe("number"); + expect(typeof response.inclusion_probabilities[0].in_active).toBe("number"); + expect(typeof response.delta_max).toBe("number"); + }); + + it("Get all mixnodes", async (): Promise<void> => { + const response = await status.getDetailedMixnodes(); + expect(typeof response[0].stake_saturation).toBe("string"); + }); + + it("Get all rewarded mixnodes", async (): Promise<void> => { + const response = await status.getDetailedRewardedMixnodes(); + expect(typeof response[0].mixnode_details.rewarding_details.last_rewarded_epoch).toBe("number"); + }); + + it("Get all active mixnodes", async (): Promise<void> => { + const response = await status.getDetailedActiveMixnodes(); + + expect(typeof response[0].mixnode_details.bond_information.layer).toBe("number"); + }); +}); + +describe("Compute mixnode reward estimation", (): void => { + beforeAll(async (): Promise<void> => { + status = new Status(); + config = ConfigHandler.getInstance(); + }); + it("with correct data", async (): Promise<void> => { + const response = await status.sendMixnodeRewardEstimatedComputation(63); + + expect(typeof response.estimation.delegates).toBe("string"); + }); +}); diff --git a/validator-api/tests/jest.config.js b/nym-api/tests/jest.config.js similarity index 100% rename from validator-api/tests/jest.config.js rename to nym-api/tests/jest.config.js diff --git a/nym-api/tests/package-lock.json b/nym-api/tests/package-lock.json new file mode 100644 index 0000000000..7e89111737 --- /dev/null +++ b/nym-api/tests/package-lock.json @@ -0,0 +1,8401 @@ +{ + "name": "validator-api-test-suite", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "validator-api-test-suite", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "axios": "^0.27.2", + "eslint": "^8.21.0", + "form-data": "4.0.0", + "json-stringify-safe": "5.0.1", + "tslog": "3.3.3", + "uuid": "8.3.2", + "yaml": "^2.1.1" + }, + "devDependencies": { + "@types/jest": "28.1.6", + "@types/mocha": "^10.0.1", + "@typescript-eslint/eslint-plugin": "^5.12.1", + "@typescript-eslint/parser": "^5.33.0", + "axios-mock-adapter": "^1.20.0", + "eslint-config-prettier": "^8.4.0", + "eslint-plugin-prettier": "^4.0.0", + "jest": "^28.1.3", + "jest-junit": "^14.0.0", + "prettier": "2.7.1", + "process": "0.11.10", + "ts-jest": "28.0.7", + "typescript": "^4.7.4", + "uuidv4": "^6.2.12" + }, + "engines": { + "node": "18.1.0", + "npm": "8.x" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz", + "integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz", + "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.10", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.10", + "@babel/types": "^7.18.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", + "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", + "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", + "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", + "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", + "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz", + "integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", + "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz", + "integrity": "sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.11", + "@babel/types": "^7.18.10", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz", + "integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/core": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", + "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/reporters": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.1.3", + "jest-config": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-resolve-dependencies": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "jest-watcher": "^28.1.3", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "dev": true, + "dependencies": { + "expect": "^28.1.3", + "jest-snapshot": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "dependencies": { + "jest-get-type": "^28.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "28.1.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", + "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.13", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", + "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", + "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.27", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.27.tgz", + "integrity": "sha512-K7C7IlQ3zLePEZleUN21ceBA2aLcMnLHTLph8QWk1JK37L90obdpY+QGY8bXMKxf1ht1Z0MNewvXxWv0oGDYFg==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.0.tgz", + "integrity": "sha512-v4Vwdko+pgymgS+A2UIaJru93zQd85vIGWObM5ekZNdXCKtDYqATlEYnWgfo86Q6I1Lh0oXnksDnMU1cwmlPDw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "28.1.6", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.6.tgz", + "integrity": "sha512-0RbGAFMfcBJKOmqRazM8L98uokwuwD5F8rHrv/ZMbrZBwVOWZUyPG6VFNscjYr/vjM3Vu4fRrCPbOs42AfemaQ==", + "dev": true, + "dependencies": { + "jest-matcher-utils": "^28.0.0", + "pretty-format": "^28.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.7.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.1.tgz", + "integrity": "sha512-GKX1Qnqxo4S+Z/+Z8KKPLpH282LD7jLHWJcVryOflnsnH+BtSDfieR6ObwBMwpnNws0bUK8GI7z0unQf9bARNQ==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", + "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", + "integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz", + "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/type-utils": "5.33.0", + "@typescript-eslint/utils": "5.33.0", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz", + "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/typescript-estree": "5.33.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", + "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/visitor-keys": "5.33.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz", + "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.33.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", + "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", + "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/visitor-keys": "5.33.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz", + "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/typescript-estree": "5.33.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", + "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.33.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "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==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/axios-mock-adapter": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.21.2.tgz", + "integrity": "sha512-jzyNxU3JzB2XVhplZboUcF0YDs7xuExzoRSHXPHr+UQajaGmcTqvkkUADgkVI2WkGlpZ1zZlMVdcTMU0ejV8zQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" + }, + "peerDependencies": { + "axios": ">= 0.17.0" + } + }, + "node_modules/babel-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", + "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^28.1.3", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^28.1.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", + "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", + "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^28.1.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001375", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001375.tgz", + "integrity": "sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", + "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", + "dev": true + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.215", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.215.tgz", + "integrity": "sha512-vqZxT8C5mlDZ//hQFhneHmOLnj1LhbzxV0+I1yqHV8SB1Oo4Y5Ne9+qQhwHl7O1s9s9cRuo2l5CoLEHdhMTwZg==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz", + "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==", + "dependencies": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.3", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", + "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==" + }, + "node_modules/follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", + "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "dev": true, + "dependencies": { + "@jest/core": "^28.1.3", + "@jest/types": "^28.1.3", + "import-local": "^3.0.2", + "jest-cli": "^28.1.3" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", + "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", + "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "p-limit": "^3.1.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-cli": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", + "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", + "dev": true, + "dependencies": { + "@jest/core": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", + "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^28.1.3", + "@jest/types": "^28.1.3", + "babel-jest": "^28.1.3", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^28.1.3", + "jest-environment-node": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", + "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-each": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", + "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "jest-util": "^28.1.3", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", + "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", + "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-junit": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-14.0.0.tgz", + "integrity": "sha512-kALvBDegstTROfDGXH71UGD7k5g7593Y1wuX1wpWT+QTYcBbmtuGOA8UlAt56zo/B2eMIOcaOVEON3j0VXVa4g==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", + "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", + "dev": true, + "dependencies": { + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", + "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", + "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^28.0.2", + "jest-snapshot": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-runner": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", + "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/environment": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.1.1", + "jest-environment-node": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-leak-detector": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-resolve": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-util": "^28.1.3", + "jest-watcher": "^28.1.3", + "jest-worker": "^28.1.3", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", + "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/globals": "^28.1.3", + "@jest/source-map": "^28.1.2", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", + "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-haste-map": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "natural-compare": "^1.4.0", + "pretty-format": "^28.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", + "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "leven": "^3.1.0", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "28.0.7", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.7.tgz", + "integrity": "sha512-wWXCSmTwBVmdvWrOpYhal79bDpioDy4rTT+0vyUnE3ZzM7LOAAGG9NXwzkEL/a516rQEgnMmS/WKP9jBPCVJyA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^28.0.0", + "json5": "^2.2.1", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^28.0.0", + "babel-jest": "^28.0.0", + "jest": "^28.0.0", + "typescript": ">=4.3" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tslog": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/tslog/-/tslog-3.3.3.tgz", + "integrity": "sha512-lGrkndwpAohZ9ntQpT+xtUw5k9YFV1DjsksiWQlBSf82TTqsSAWBARPRD9juI730r8o3Awpkjp2aXy9k+6vr+g==", + "dependencies": { + "source-map-support": "^0.5.21" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tslog/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", + "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uuidv4": { + "version": "6.2.13", + "resolved": "https://registry.npmjs.org/uuidv4/-/uuidv4-6.2.13.tgz", + "integrity": "sha512-AXyzMjazYB3ovL3q051VLH06Ixj//Knx7QnUSi1T//Ie3io6CpsPu9nVMOx5MoLWh6xV0B9J0hIaxungxXUbPQ==", + "dev": true, + "dependencies": { + "@types/uuid": "8.3.4", + "uuid": "8.3.2" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" + }, + "node_modules/v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz", + "integrity": "sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz", + "integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==", + "dev": true + }, + "@babel/core": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz", + "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.10", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.10", + "@babel/types": "^7.18.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", + "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", + "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "dev": true, + "requires": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", + "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", + "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", + "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "dev": true, + "requires": { + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz", + "integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", + "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + } + }, + "@babel/traverse": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz", + "integrity": "sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.11", + "@babel/types": "^7.18.10", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz", + "integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==" + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", + "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/reporters": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.1.3", + "jest-config": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-resolve-dependencies": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "jest-watcher": "^28.1.3", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "dev": true, + "requires": { + "expect": "^28.1.3", + "jest-snapshot": "^28.1.3" + } + }, + "@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "requires": { + "jest-get-type": "^28.0.2" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" + } + }, + "@jest/reporters": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" + } + }, + "@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/source-map": { + "version": "28.1.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", + "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.13", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", + "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "dev": true, + "requires": { + "@jest/test-result": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", + "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + } + }, + "@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@sinclair/typebox": { + "version": "0.24.27", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.27.tgz", + "integrity": "sha512-K7C7IlQ3zLePEZleUN21ceBA2aLcMnLHTLph8QWk1JK37L90obdpY+QGY8bXMKxf1ht1Z0MNewvXxWv0oGDYFg==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@types/babel__core": { + "version": "7.1.19", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", + "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.0.tgz", + "integrity": "sha512-v4Vwdko+pgymgS+A2UIaJru93zQd85vIGWObM5ekZNdXCKtDYqATlEYnWgfo86Q6I1Lh0oXnksDnMU1cwmlPDw==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "28.1.6", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.6.tgz", + "integrity": "sha512-0RbGAFMfcBJKOmqRazM8L98uokwuwD5F8rHrv/ZMbrZBwVOWZUyPG6VFNscjYr/vjM3Vu4fRrCPbOs42AfemaQ==", + "dev": true, + "requires": { + "jest-matcher-utils": "^28.0.0", + "pretty-format": "^28.0.0" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "@types/node": { + "version": "18.7.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.1.tgz", + "integrity": "sha512-GKX1Qnqxo4S+Z/+Z8KKPLpH282LD7jLHWJcVryOflnsnH+BtSDfieR6ObwBMwpnNws0bUK8GI7z0unQf9bARNQ==", + "dev": true + }, + "@types/prettier": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", + "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", + "integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz", + "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/type-utils": "5.33.0", + "@typescript-eslint/utils": "5.33.0", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz", + "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/typescript-estree": "5.33.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", + "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/visitor-keys": "5.33.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz", + "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "5.33.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", + "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", + "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/visitor-keys": "5.33.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz", + "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.33.0", + "@typescript-eslint/types": "5.33.0", + "@typescript-eslint/typescript-estree": "5.33.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", + "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.33.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==" + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "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==", + "requires": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "axios-mock-adapter": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.21.2.tgz", + "integrity": "sha512-jzyNxU3JzB2XVhplZboUcF0YDs7xuExzoRSHXPHr+UQajaGmcTqvkkUADgkVI2WkGlpZ1zZlMVdcTMU0ejV8zQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" + } + }, + "babel-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", + "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "dev": true, + "requires": { + "@jest/transform": "^28.1.3", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^28.1.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", + "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", + "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^28.1.3", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" + } + }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001375", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001375.tgz", + "integrity": "sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "ci-info": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", + "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "requires": { + "esutils": "^2.0.2" + } + }, + "electron-to-chromium": { + "version": "1.4.215", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.215.tgz", + "integrity": "sha512-vqZxT8C5mlDZ//hQFhneHmOLnj1LhbzxV0+I1yqHV8SB1Oo4Y5Ne9+qQhwHl7O1s9s9cRuo2l5CoLEHdhMTwZg==", + "dev": true + }, + "emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "eslint": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz", + "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==", + "requires": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.3", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + } + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} + }, + "eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" + }, + "espree": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", + "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "dev": true, + "requires": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==" + }, + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", + "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "dev": true, + "requires": { + "@jest/core": "^28.1.3", + "@jest/types": "^28.1.3", + "import-local": "^3.0.2", + "jest-cli": "^28.1.3" + } + }, + "jest-changed-files": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", + "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", + "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "p-limit": "^3.1.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-cli": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", + "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", + "dev": true, + "requires": { + "@jest/core": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", + "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^28.1.3", + "@jest/types": "^28.1.3", + "babel-jest": "^28.1.3", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^28.1.3", + "jest-environment-node": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, + "jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + } + }, + "jest-docblock": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", + "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", + "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "jest-util": "^28.1.3", + "pretty-format": "^28.1.3" + } + }, + "jest-environment-node": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", + "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "jest-haste-map": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", + "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-junit": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-14.0.0.tgz", + "integrity": "sha512-kALvBDegstTROfDGXH71UGD7k5g7593Y1wuX1wpWT+QTYcBbmtuGOA8UlAt56zo/B2eMIOcaOVEON3j0VXVa4g==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + } + }, + "jest-leak-detector": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", + "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", + "dev": true, + "requires": { + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + } + }, + "jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + } + }, + "jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "dev": true + }, + "jest-resolve": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", + "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", + "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", + "dev": true, + "requires": { + "jest-regex-util": "^28.0.2", + "jest-snapshot": "^28.1.3" + } + }, + "jest-runner": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", + "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/environment": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.1.1", + "jest-environment-node": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-leak-detector": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-resolve": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-util": "^28.1.3", + "jest-watcher": "^28.1.3", + "jest-worker": "^28.1.3", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + } + }, + "jest-runtime": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", + "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/globals": "^28.1.3", + "@jest/source-map": "^28.1.2", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + } + }, + "jest-snapshot": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", + "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-haste-map": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "natural-compare": "^1.4.0", + "pretty-format": "^28.1.3", + "semver": "^7.3.5" + } + }, + "jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", + "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "leven": "^3.1.0", + "pretty-format": "^28.1.3" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "dev": true, + "requires": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-jest": { + "version": "28.0.7", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.7.tgz", + "integrity": "sha512-wWXCSmTwBVmdvWrOpYhal79bDpioDy4rTT+0vyUnE3ZzM7LOAAGG9NXwzkEL/a516rQEgnMmS/WKP9jBPCVJyA==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^28.0.0", + "json5": "^2.2.1", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tslog": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/tslog/-/tslog-3.3.3.tgz", + "integrity": "sha512-lGrkndwpAohZ9ntQpT+xtUw5k9YFV1DjsksiWQlBSf82TTqsSAWBARPRD9juI730r8o3Awpkjp2aXy9k+6vr+g==", + "requires": { + "source-map-support": "^0.5.21" + }, + "dependencies": { + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", + "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "uuidv4": { + "version": "6.2.13", + "resolved": "https://registry.npmjs.org/uuidv4/-/uuidv4-6.2.13.tgz", + "integrity": "sha512-AXyzMjazYB3ovL3q051VLH06Ixj//Knx7QnUSi1T//Ie3io6CpsPu9nVMOx5MoLWh6xV0B9J0hIaxungxXUbPQ==", + "dev": true, + "requires": { + "@types/uuid": "8.3.4", + "uuid": "8.3.2" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" + }, + "v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "write-file-atomic": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.1.tgz", + "integrity": "sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==" + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + } + } +} diff --git a/validator-api/tests/package.json b/nym-api/tests/package.json similarity index 82% rename from validator-api/tests/package.json rename to nym-api/tests/package.json index deb5ee46b1..2beba94f14 100644 --- a/validator-api/tests/package.json +++ b/nym-api/tests/package.json @@ -7,10 +7,11 @@ "test": "test" }, "scripts": { - "test": "jest --forceExit --detectOpenHandles --passWithNoTests", + "test:qa": "TEST_ENV=qa jest --forceExit --detectOpenHandles --passWithNoTests", + "test:prod": "TEST_ENV=prod jest --forceExit --detectOpenHandles --passWithNoTests", "build": "tsc", "lint": "eslint --ext .js,.ts,.tsx .", - "lint:fix": "eslint --fix", + "lint:fix": "eslint src --fix", "cleanup": "rm -rf node_modules; rm -rf dist; yarn install" }, "author": "Nymtech", @@ -33,6 +34,7 @@ }, "devDependencies": { "@types/jest": "28.1.6", + "@types/mocha": "^10.0.1", "@typescript-eslint/eslint-plugin": "^5.12.1", "@typescript-eslint/parser": "^5.33.0", "axios-mock-adapter": "^1.20.0", diff --git a/nym-api/tests/src/config/config.yaml b/nym-api/tests/src/config/config.yaml new file mode 100644 index 0000000000..83a9d3fa92 --- /dev/null +++ b/nym-api/tests/src/config/config.yaml @@ -0,0 +1,16 @@ +common: + request_headers: + Accept: application/json + Content-Type: application/json +qa: + api_base_url: https://qwerty-validator-api.qa.nymte.ch/api/v1 + mix_id: 63 + identity_key: 4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz + gateway_identity: 336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9 + log_level: error +prod: + api_base_url: https://validator.nymtech.net/api/v1 + mixnode_identity: DLdMKLPywEy1vnu3yPrtXvzY7fw1puiiHpA9n9UQatiQ + gateway_identity: CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM + log_level: error + time_zone: utc diff --git a/validator-api/tests/src/config/configHandler.ts b/nym-api/tests/src/config/configHandler.ts similarity index 92% rename from validator-api/tests/src/config/configHandler.ts rename to nym-api/tests/src/config/configHandler.ts index be37be979d..44177f0866 100644 --- a/validator-api/tests/src/config/configHandler.ts +++ b/nym-api/tests/src/config/configHandler.ts @@ -16,13 +16,14 @@ class ConfigHandler { log_level: TLogLevelName; time_zone: string; api_base_url: string; - mixnode_identity: string; + mix_id: number; + identity_key: string; gateway_identity: string; }; private constructor() { this.setCommonConfig(); - this.setEnvironmentConfig(process.env.TEST_ENV || "prod"); + this.setEnvironmentConfig(process.env.TEST_ENV || "qa" || "prod"); } public static getInstance(): ConfigHandler { diff --git a/nym-api/tests/src/endpoints/CirculatingSupply.ts b/nym-api/tests/src/endpoints/CirculatingSupply.ts new file mode 100644 index 0000000000..022a143c4b --- /dev/null +++ b/nym-api/tests/src/endpoints/CirculatingSupply.ts @@ -0,0 +1,29 @@ +import { Detailed } from "../types/CirculatingSupplyTypes"; +import { APIClient } from "./abstracts/APIClient"; + +export default class ContractCache extends APIClient { + constructor() { + super("/"); + } + + public async getCirculatingSupply(): Promise<Detailed> { + const response = await this.restClient.sendGet({ + route: `circulating-supply`, + }); + return response.data; + } + + public async getTotalSupplyValue(): Promise<number> { + const response = await this.restClient.sendGet({ + route: `circulating-supply/total-supply-value`, + }); + return response.data; + } + + public async getCirculatingSupplyValue(): Promise<number> { + const response = await this.restClient.sendGet({ + route: `circulating-supply/circulating-supply-value`, + }); + return response.data; + } +} diff --git a/nym-api/tests/src/endpoints/ContractCache.ts b/nym-api/tests/src/endpoints/ContractCache.ts new file mode 100644 index 0000000000..16ae21f9aa --- /dev/null +++ b/nym-api/tests/src/endpoints/ContractCache.ts @@ -0,0 +1,92 @@ +import { + MixnodesDetailed, + AllGateways, + AllMixnodes, + EpochRewardParams, + CurrentEpoch, +} from "../types/ContractCacheTypes"; +import { APIClient } from "./abstracts/APIClient"; + +export default class ContractCache extends APIClient { + constructor() { + super("/"); + } + + public async getMixnodes(): Promise<AllMixnodes[]> { + const response = await this.restClient.sendGet({ + route: `mixnodes`, + }); + return response.data; + } + + public async getMixnodesDetailed(): Promise<MixnodesDetailed[]> { + const response = await this.restClient.sendGet({ + route: `mixnodes/detailed`, + }); + + return response.data; + } + + public async getGateways(): Promise<AllGateways[]> { + const response = await this.restClient.sendGet({ + route: `gateways`, + }); + return response.data; + } + + public async getActiveMixnodes(): Promise<AllMixnodes[]> { + const response = await this.restClient.sendGet({ + route: `mixnodes/active`, + }); + return response.data; + } + + public async getActiveMixnodesDetailed(): Promise<MixnodesDetailed[]> { + const response = await this.restClient.sendGet({ + route: `mixnodes/active/detailed`, + }); + return response.data; + } + + public async getRewardedMixnodes(): Promise<AllMixnodes[]> { + const response = await this.restClient.sendGet({ + route: `mixnodes/rewarded`, + }); + return response.data; + } + + public async getRewardedMixnodesDetailed(): Promise<MixnodesDetailed[]> { + const response = await this.restClient.sendGet({ + route: `mixnodes/rewarded/detailed`, + }); + return response.data; + } + + public async getBlacklistedMixnodes(): Promise<[]> { + const response = await this.restClient.sendGet({ + route: `mixnodes/blacklisted`, + }); + return response.data; + } + + public async getBlacklistedGateways(): Promise<[]> { + const response = await this.restClient.sendGet({ + route: `gateways/blacklisted`, + }); + return response.data; + } + + public async getEpochRewardParams(): Promise<EpochRewardParams> { + const response = await this.restClient.sendGet({ + route: `epoch/reward_params`, + }); + return response.data; + } + + public async getCurrentEpoch(): Promise<CurrentEpoch> { + const response = await this.restClient.sendGet({ + route: `epoch/current`, + }); + return response.data; + } +} diff --git a/nym-api/tests/src/endpoints/Status.ts b/nym-api/tests/src/endpoints/Status.ts new file mode 100644 index 0000000000..096e90cd88 --- /dev/null +++ b/nym-api/tests/src/endpoints/Status.ts @@ -0,0 +1,180 @@ +import { + ActiveStatus, + AvgUptime, + CoreCount, + DetailedGateway, + DetailedMixnodes, + InclusionProbabilities, + InclusionProbability, + NodeHistory, + NoUptime, + Report, + RewardEstimation, + StakeSaturation, +} from "../types/StatusInterfaces"; +import { APIClient } from "./abstracts/APIClient"; + +export default class Status extends APIClient { + constructor() { + super("/status"); + } + + // GATEWAYS + + public async getDetailedGateways(): Promise<DetailedGateway[]> { + const response = await this.restClient.sendGet({ + route: `/gateways/detailed`, + }); + + return response.data; + } + + public async getGatewayStatusReport(identity_key: string): Promise<Report> { + const response = await this.restClient.sendGet({ + route: `/gateway/${identity_key}/report`, + }); + + return response.data; + } + + public async getGatewayHistory(identity_key: string): Promise<NodeHistory | NoUptime> { + const response = await this.restClient.sendGet({ + route: `/gateway/${identity_key}/history`, + }); + + return response.data; + } + + public async getGatewayCoreCount(identity_key: string): Promise<CoreCount> { + const response = await this.restClient.sendGet({ + route: `/gateway/${identity_key}/core-status-count`, + }); + + return response.data; + } + + // MIXNODES + + public async getMixnodeStatusReport(mix_id: number): Promise<Report> { + const response = await this.restClient.sendGet({ + route: `/mixnode/${mix_id}/report`, + }); + + return response.data; + } + + public async getMixnodeStakeSaturation( + mix_id: number + ): Promise<StakeSaturation> { + const response = await this.restClient.sendGet({ + route: `/mixnode/${mix_id}/stake-saturation`, + }); + + return response.data; + } + + public async getMixnodeCoreCount(mix_id: number): Promise<CoreCount> { + const response = await this.restClient.sendGet({ + route: `/mixnode/${mix_id}/core-status-count`, + }); + + return response.data; + } + + public async getMixnodeRewardComputation( + mix_id: number + ): Promise<RewardEstimation> { + const response = await this.restClient.sendGet({ + route: `/mixnode/${mix_id}/reward-estimation`, + }); + + return response.data; + } + + public async sendMixnodeRewardEstimatedComputation( + mix_id: number + ): Promise<RewardEstimation> { + const response = await this.restClient.sendPost({ + route: `/mixnode/${mix_id}/compute-reward-estimation`, + data: { + // performance: "10", + active_in_rewarded_set: true, + // pledge_amount: 10, + // total_delegation: 2000, + // interval_operating_cost: { + // denom: "unym", + // amount: "250000000" + // }, + // profit_margin_percent: 10 + }, + }); + + return response.data; + } + + public async getMixnodeHistory(mix_id: number): Promise<NodeHistory | NoUptime> { + const response = await this.restClient.sendGet({ + route: `/mixnode/${mix_id}/history`, + }); + + return response.data; + } + + public async getMixnodeAverageUptime(mix_id: number): Promise<AvgUptime> { + const response = await this.restClient.sendGet({ + route: `/mixnode/${mix_id}/avg_uptime`, + }); + + return response.data; + } + + public async getMixnodeInclusionProbability( + mix_id: number + ): Promise<InclusionProbability> { + const response = await this.restClient.sendGet({ + route: `/mixnode/${mix_id}/inclusion-probability`, + }); + + return response.data; + } + + public async getMixnodeStatus(mix_id: number): Promise<ActiveStatus> { + const response = await this.restClient.sendGet({ + route: `/mixnode/${mix_id}/status`, + }); + + return response.data; + } + + public async getAllMixnodeInclusionProbability(): Promise<InclusionProbabilities> { + const response = await this.restClient.sendGet({ + route: `/mixnodes/inclusion_probability`, + }); + + return response.data; + } + + public async getDetailedMixnodes(): Promise<DetailedMixnodes[]> { + const response = await this.restClient.sendGet({ + route: `/mixnodes/detailed`, + }); + + return response.data; + } + + public async getDetailedRewardedMixnodes(): Promise<DetailedMixnodes[]> { + const response = await this.restClient.sendGet({ + route: `/mixnodes/rewarded/detailed`, + }); + + return response.data; + } + + public async getDetailedActiveMixnodes(): Promise<DetailedMixnodes[]> { + const response = await this.restClient.sendGet({ + route: `/mixnodes/active/detailed`, + }); + + return response.data; + } +} diff --git a/validator-api/tests/src/endpoints/abstracts/APIClient.ts b/nym-api/tests/src/endpoints/abstracts/APIClient.ts similarity index 100% rename from validator-api/tests/src/endpoints/abstracts/APIClient.ts rename to nym-api/tests/src/endpoints/abstracts/APIClient.ts diff --git a/validator-api/tests/src/restClient/RestClient.ts b/nym-api/tests/src/restClient/RestClient.ts similarity index 100% rename from validator-api/tests/src/restClient/RestClient.ts rename to nym-api/tests/src/restClient/RestClient.ts diff --git a/nym-api/tests/src/types/CirculatingSupplyTypes.ts b/nym-api/tests/src/types/CirculatingSupplyTypes.ts new file mode 100644 index 0000000000..6ea3454d9a --- /dev/null +++ b/nym-api/tests/src/types/CirculatingSupplyTypes.ts @@ -0,0 +1,26 @@ +export type Detailed = { + total_supply: InitialSupply; + mixmining_reserve: MixminingReserve; + vesting_tokens: VestingTokens; + circulating_supply: CirculatingSupply; +}; + +export type InitialSupply = { + demon: "unym"; + amount: string; +}; + +export type MixminingReserve = { + demon: "unym"; + amount: string; +}; + +export type VestingTokens = { + demon: "unym"; + amount: string; +}; + +export type CirculatingSupply = { + demon: "unym"; + amount: string; +}; diff --git a/nym-api/tests/src/types/ContractCacheTypes.ts b/nym-api/tests/src/types/ContractCacheTypes.ts new file mode 100644 index 0000000000..bf83b0cd95 --- /dev/null +++ b/nym-api/tests/src/types/ContractCacheTypes.ts @@ -0,0 +1,125 @@ +export interface AllMixnodes { + bond_information: BondInformation; + rewarding_details: RewardingDetails; +} + +export interface BondInformation { + mix_id: number; + owner: string; + original_pledge: OriginalPledge; + layer: number; + mix_node: Mixnode; + proxy: string; + bonding_height: number; + is_unbonding: boolean; +} + +export interface RewardingDetails { + cost_params: CostParams; + operator: string; + delegates: string; + total_unit_reward: string; + unit_delegation: string; + last_rewarded_epoch: number; + unique_delegations: number; +} + +export interface CostParams { + profit_margin_percent: string; + interval_operating_cost: IntervalOperatingCost; +} + +export interface IntervalOperatingCost { + denom: string; + amount: string; +} + +export interface OriginalPledge { + denom: string; + amount: string; +} + +export interface TotalDelegation { + denom: string; + amount: string; +} + +export interface Mixnode { + host: string; + mix_port: number; + verloc_port: number; + http_api_port: number; + sphinx_key: string; + identity_key: string; + version: string; +} + +export interface MixnodeBond { + pledge_amount: OriginalPledge; + total_delegation: TotalDelegation; + owner: string; + layer: string; + block_height: string; + mix_node: Mixnode; + proxy: string; + accumulated_rewards: string; +} + +export interface MixnodesDetailed { + mixnode_details: AllMixnodes; + stake_saturation: string; + uncapped_stake_saturation: string; + performance: string; + estimated_operator_apy: string; + estimated_delegators_apy: string; + family: string; +} + +export interface Gateway { + host: string; + mix_port: number; + clients_port: number; + location: string; + sphinx_key: string; + identity_key: string; + version: string; +} + +export interface AllGateways { + pledge_amount: OriginalPledge; + owner: string; + block_height: number; + gateway: Gateway; + proxy: string; +} + +export interface EpochRewardParams { + interval: Interval; + rewarded_set_size: number; + active_set_size: number; +} + +export interface Interval { + reward_pool: string; + staking_supply: string; + staking_supply_scale_factor: string; + epoch_reward_budget: string; + stake_saturation_point: string; + sybil_resistance: string; + active_set_work_factor: string; + interval_pool_emission: string; +} + +export interface CurrentEpoch { + id: number; + epochs_in_interval: number; + current_epoch_start: string; + current_epoch_id: number; + epoch_length: EpochLength; + total_elapsed_epochs: number; +} + +export interface EpochLength { + secs: number; + nanos: number; +} diff --git a/nym-api/tests/src/types/StatusInterfaces.ts b/nym-api/tests/src/types/StatusInterfaces.ts new file mode 100644 index 0000000000..069c509ace --- /dev/null +++ b/nym-api/tests/src/types/StatusInterfaces.ts @@ -0,0 +1,240 @@ +export interface CurrentEpoch { + epoch_reward_pool: string; + rewarded_set_size: string; + active_set_size: string; + staking_supply: string; + sybil_resistance_percent: number; + active_set_work_factor: number; +} + +export interface Epoch { + id: number; + epochs_in_interval: number; + current_epoch_start: string; + current_epoch_id: number; + epoch_length: EpochLength; + total_elapsed_epochs: number; +} + +export interface EpochLength { + secs: number; + nanos: number; +} + +export interface Node { + reward_blockstamp: number; + uptime: string; + in_active_set: boolean; +} + +export interface Estimation { + total_node_reward: string; + operator: string; + delegates: string; + operating_cost: string; +} + +export interface RewardEstimation { + estimation: Estimation; + reward_params: RewardParams; + epoch: Epoch; + as_at: number; +} + +export interface RewardParams { + interval: Interval; + rewarded_set_size: number; + active_set_size: number; +} + +export interface ComputeRewardEstimation { + performance: string; + active_in_rewarded_set: boolean; + pledge_amount: number; + total_delegation: number; + interval_operating_cost: IntervalOperatingCost; + profit_margin_percent: string; +} + +export interface IntervalOperatingCost { + denom: string; + amount: string; +} + +export interface Interval { + reward_pool: string; + staking_supply: string; + staking_supply_scale_factor: string; + epoch_reward_budget: string; + stake_saturation_point: string; + sybil_resistance: string; + active_set_work_factor: string; + interval_pool_emission: string; +} + +export interface StakeSaturation { + saturation: string; + uncapped_saturation: string; + as_at: number; +} + +export interface AvgUptime { + mix_id: number; + avg_uptime: number; +} + +export interface SingleInclusionProbability { + in_active: number; + in_reserve: number; +} + +export interface InclusionProbability { + mix_id: number; + in_active: string; + in_reserve: string; +} + +export interface InclusionProbabilities { + inclusion_probabilities: InclusionProbability[]; + samples: number; + elapsed: Elapsed; + delta_max: number; + delta_l2: number; + as_at: number; +} + +export interface Elapsed { + secs: number; + nanos: number; +} + +export interface Report { + mix_id: number; + identity: string; + owner: string; + most_recent: number; + last_hour: number; + last_day: number; +} + +export interface History { + date: string; + uptime: number; +} + +export interface NodeHistory { + mix_id: number; + identity: string; + owner: string; + history: History[]; +} + +export interface NoUptime { + message: string; +} + +export interface CoreCount { + mix_id: number; + identity: string; + count: number; +} + +export interface ActiveStatus { + status: string; +} + +export interface PledgeAmount { + denom: string; + amount: string; +} + +export interface Gateway { + host: string; + mix_port: number; + clients_port: number; + location: string; + sphinx_key: string; + identity_key: string; + version: string; +} + +export interface GatewayBond { + pledge_amount: PledgeAmount; + owner: string; + block_height: number; + gateway: Gateway; + proxy?: any; +} + +export interface nodePerformance { + most_recent: string; + last_hour: string; + last_24h: string; +} + +export interface DetailedGateway { + gateway_bond: GatewayBond; + performance: string; + node_performance: nodePerformance; +} + +export interface OriginalPledge { + denom: string; + amount: string; +} + +export interface MixNode { + host: string; + mix_port: number; + verloc_port: number; + http_api_port: number; + sphinx_key: string; + identity_key: string; + version: string; +} + +export interface BondInformation { + mix_id: number; + owner: string; + original_pledge: OriginalPledge; + layer: string; + mix_node: MixNode; + proxy: string; + bonding_height: number; + is_unbonding: boolean; +} + +export interface IntervalOperatingCost { + denom: string; + amount: string; +} + +export interface CostParams { + profit_margin_percent: string; + interval_operating_cost: IntervalOperatingCost; +} + +export interface RewardingDetails { + cost_params: CostParams; + operator: string; + delegates: string; + total_unit_reward: string; + unit_delegation: string; + last_rewarded_epoch: number; + unique_delegations: number; +} + +export interface MixnodeDetails { + bond_information: BondInformation; + rewarding_details: RewardingDetails; +} + +export interface DetailedMixnodes { + mixnode_details: MixnodeDetails; + stake_saturation: string; + uncapped_stake_saturation: string; + performance: string; + estimated_operator_apy: string; + estimated_delegators_apy: string; + family: string; +} diff --git a/validator-api/tests/tsconfig.eslint.json b/nym-api/tests/tsconfig.eslint.json similarity index 100% rename from validator-api/tests/tsconfig.eslint.json rename to nym-api/tests/tsconfig.eslint.json diff --git a/validator-api/tests/tsconfig.json b/nym-api/tests/tsconfig.json similarity index 90% rename from validator-api/tests/tsconfig.json rename to nym-api/tests/tsconfig.json index c25d4218fa..60fd578d9a 100644 --- a/validator-api/tests/tsconfig.json +++ b/nym-api/tests/tsconfig.json @@ -20,6 +20,6 @@ "typeRoots": ["node_modules/@types"], "alwaysStrict": true }, - "include": ["src/**/*", "functional_test/**/*"], + "include": ["src/**/*", "tests/functional_test/*/*"], "exclude": ["unit_test/**/*"] } diff --git a/validator-api/tests/yarn.lock b/nym-api/tests/yarn.lock similarity index 95% rename from validator-api/tests/yarn.lock rename to nym-api/tests/yarn.lock index e13dae8701..09186f794d 100644 --- a/validator-api/tests/yarn.lock +++ b/nym-api/tests/yarn.lock @@ -292,7 +292,7 @@ "@eslint/eslintrc@^1.3.0": version "1.3.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz" integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== dependencies: ajv "^6.12.4" @@ -307,7 +307,7 @@ "@humanwhocodes/config-array@^0.10.4": version "0.10.4" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.4.tgz#01e7366e57d2ad104feea63e72248f22015c520c" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz" integrity sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw== dependencies: "@humanwhocodes/object-schema" "^1.2.1" @@ -316,12 +316,12 @@ "@humanwhocodes/gitignore-to-minimatch@^1.0.2": version "1.0.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz#316b0a63b91c10e53f242efb4ace5c3b34e8728d" + resolved "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz" integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA== "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@istanbuljs/load-nyc-config@^1.0.0": @@ -685,6 +685,11 @@ resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/mocha@^10.0.1": + version "10.0.1" + resolved "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz" + integrity sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q== + "@types/node@*": version "18.7.1" resolved "https://registry.npmjs.org/@types/node/-/node-18.7.1.tgz" @@ -734,7 +739,7 @@ "@typescript-eslint/parser@^5.33.0": version "5.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.33.0.tgz#26ec3235b74f0667414613727cb98f9b69dc5383" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz" integrity sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w== dependencies: "@typescript-eslint/scope-manager" "5.33.0" @@ -799,17 +804,17 @@ acorn-jsx@^5.3.2: version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.8.0: version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -865,7 +870,7 @@ argparse@^1.0.7: argparse@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== array-union@^2.1.0: @@ -1140,7 +1145,7 @@ dedent@^0.7.0: deep-is@^0.1.3: version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@^4.2.2: @@ -1172,7 +1177,7 @@ dir-glob@^3.0.1: doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" @@ -1216,7 +1221,7 @@ escape-string-regexp@^2.0.0: escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== eslint-config-prettier@^8.4.0: @@ -1241,7 +1246,7 @@ eslint-scope@^5.1.1: eslint-scope@^7.1.1: version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz" integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== dependencies: esrecurse "^4.3.0" @@ -1266,7 +1271,7 @@ eslint-visitor-keys@^3.3.0: eslint@^8.21.0: version "8.21.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.21.0.tgz#1940a68d7e0573cef6f50037addee295ff9be9ef" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz" integrity sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA== dependencies: "@eslint/eslintrc" "^1.3.0" @@ -1311,7 +1316,7 @@ eslint@^8.21.0: espree@^9.3.2, espree@^9.3.3: version "9.3.3" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.3.tgz#2dd37c4162bb05f433ad3c1a52ddf8a49dc08e9d" + resolved "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz" integrity sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng== dependencies: acorn "^8.8.0" @@ -1325,7 +1330,7 @@ esprima@^4.0.0: esquery@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" @@ -1349,7 +1354,7 @@ estraverse@^5.1.0, estraverse@^5.2.0: esutils@^2.0.2: version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== execa@^5.0.0: @@ -1411,7 +1416,7 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: fast-levenshtein@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: @@ -1430,7 +1435,7 @@ fb-watchman@^2.0.0: file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" @@ -1452,7 +1457,7 @@ find-up@^4.0.0, find-up@^4.1.0: find-up@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" @@ -1460,7 +1465,7 @@ find-up@^5.0.0: flat-cache@^3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: flatted "^3.1.0" @@ -1468,7 +1473,7 @@ flat-cache@^3.0.4: flatted@^3.1.0: version "3.2.6" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz" integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ== follow-redirects@^1.14.9: @@ -1492,7 +1497,7 @@ fs.realpath@^1.0.0: fsevents@^2.3.2: version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: @@ -1534,7 +1539,7 @@ glob-parent@^5.1.2: glob-parent@^6.0.1: version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: is-glob "^4.0.3" @@ -1558,7 +1563,7 @@ globals@^11.1.0: globals@^13.15.0: version "13.17.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" + resolved "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz" integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== dependencies: type-fest "^0.20.2" @@ -1582,7 +1587,7 @@ graceful-fs@^4.2.9: grapheme-splitter@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== has-flag@^3.0.0: @@ -1619,7 +1624,7 @@ ignore@^5.2.0: import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" @@ -2130,7 +2135,7 @@ js-yaml@^3.13.1: js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" @@ -2147,12 +2152,12 @@ json-parse-even-better-errors@^2.3.0: json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json-stringify-safe@5.0.1: @@ -2161,9 +2166,9 @@ json-stringify-safe@5.0.1: integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== json5@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== kleur@^3.0.3: version "3.0.3" @@ -2177,7 +2182,7 @@ leven@^3.1.0: levn@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" @@ -2197,7 +2202,7 @@ locate-path@^5.0.0: locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" @@ -2209,7 +2214,7 @@ lodash.memoize@4.x: lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lru-cache@^6.0.0: @@ -2333,7 +2338,7 @@ onetime@^5.1.2: optionator@^0.9.1: version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: deep-is "^0.1.3" @@ -2366,7 +2371,7 @@ p-locate@^4.1.0: p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" @@ -2378,7 +2383,7 @@ p-try@^2.0.0: parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" @@ -2442,7 +2447,7 @@ pkg-dir@^4.2.0: prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier-linter-helpers@^1.0.0: @@ -2482,7 +2487,7 @@ prompts@^2.0.1: punycode@^2.1.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== queue-microtask@^1.2.2: @@ -2514,7 +2519,7 @@ resolve-cwd@^3.0.0: resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve-from@^5.0.0: @@ -2724,7 +2729,7 @@ test-exclude@^6.0.0: text-table@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== tmpl@1.0.5: @@ -2779,7 +2784,7 @@ tsutils@^3.21.0: type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" @@ -2791,7 +2796,7 @@ type-detect@4.0.8: type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== type-fest@^0.21.3: @@ -2801,7 +2806,7 @@ type-fest@^0.21.3: typescript@^4.7.4: version "4.7.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz" integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== update-browserslist-db@^1.0.5: @@ -2814,7 +2819,7 @@ update-browserslist-db@^1.0.5: uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" @@ -2834,7 +2839,7 @@ uuidv4@^6.2.12: v8-compile-cache@^2.0.3: version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== v8-to-istanbul@^9.0.1: @@ -2862,7 +2867,7 @@ which@^2.0.1: word-wrap@^1.2.3: version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== wrap-ansi@^7.0.0: diff --git a/nym-connect/.babelrc b/nym-connect/desktop/.babelrc similarity index 100% rename from nym-connect/.babelrc rename to nym-connect/desktop/.babelrc diff --git a/nym-connect/.env.sample b/nym-connect/desktop/.env.sample similarity index 100% rename from nym-connect/.env.sample rename to nym-connect/desktop/.env.sample diff --git a/nym-connect/.eslintrc b/nym-connect/desktop/.eslintrc similarity index 100% rename from nym-connect/.eslintrc rename to nym-connect/desktop/.eslintrc diff --git a/nym-connect/.nvmrc b/nym-connect/desktop/.nvmrc similarity index 100% rename from nym-connect/.nvmrc rename to nym-connect/desktop/.nvmrc diff --git a/nym-connect/.prettierrc b/nym-connect/desktop/.prettierrc similarity index 100% rename from nym-connect/.prettierrc rename to nym-connect/desktop/.prettierrc diff --git a/nym-connect/.storybook/main.js b/nym-connect/desktop/.storybook/main.js similarity index 100% rename from nym-connect/.storybook/main.js rename to nym-connect/desktop/.storybook/main.js diff --git a/nym-connect/.storybook/mocks/tauri/app.js b/nym-connect/desktop/.storybook/mocks/tauri/app.js similarity index 100% rename from nym-connect/.storybook/mocks/tauri/app.js rename to nym-connect/desktop/.storybook/mocks/tauri/app.js diff --git a/nym-connect/.storybook/mocks/tauri/clipboard.js b/nym-connect/desktop/.storybook/mocks/tauri/clipboard.js similarity index 100% rename from nym-connect/.storybook/mocks/tauri/clipboard.js rename to nym-connect/desktop/.storybook/mocks/tauri/clipboard.js diff --git a/nym-connect/.storybook/mocks/tauri/event.js b/nym-connect/desktop/.storybook/mocks/tauri/event.js similarity index 100% rename from nym-connect/.storybook/mocks/tauri/event.js rename to nym-connect/desktop/.storybook/mocks/tauri/event.js diff --git a/nym-connect/.storybook/mocks/tauri/index.js b/nym-connect/desktop/.storybook/mocks/tauri/index.js similarity index 100% rename from nym-connect/.storybook/mocks/tauri/index.js rename to nym-connect/desktop/.storybook/mocks/tauri/index.js diff --git a/nym-connect/.storybook/mocks/tauri/notification.js b/nym-connect/desktop/.storybook/mocks/tauri/notification.js similarity index 100% rename from nym-connect/.storybook/mocks/tauri/notification.js rename to nym-connect/desktop/.storybook/mocks/tauri/notification.js diff --git a/nym-connect/.storybook/mocks/tauri/window.js b/nym-connect/desktop/.storybook/mocks/tauri/window.js similarity index 100% rename from nym-connect/.storybook/mocks/tauri/window.js rename to nym-connect/desktop/.storybook/mocks/tauri/window.js diff --git a/nym-connect/.storybook/preview-fonts.js b/nym-connect/desktop/.storybook/preview-fonts.js similarity index 100% rename from nym-connect/.storybook/preview-fonts.js rename to nym-connect/desktop/.storybook/preview-fonts.js diff --git a/nym-connect/.storybook/preview.js b/nym-connect/desktop/.storybook/preview.js similarity index 100% rename from nym-connect/.storybook/preview.js rename to nym-connect/desktop/.storybook/preview.js diff --git a/nym-connect/desktop/CHANGELOG.md b/nym-connect/desktop/CHANGELOG.md new file mode 100644 index 0000000000..4510836e7e --- /dev/null +++ b/nym-connect/desktop/CHANGELOG.md @@ -0,0 +1,138 @@ +# Changelog + +## [Unreleased] + +## [v1.1.12] (2023-03-07) + +- NymConnect - Update display for selected Service Provider ([#3116]) + +[#3116]: https://github.com/nymtech/nym/issues/3116 + +## [v1.1.11] (2023-02-28) + +- NC - add the option to manually select and use a specific Service Provider ([#2953]) + +[#2953]: https://github.com/nymtech/nym/issues/2953 + +## [v1.1.10] (2023-02-21) + +- NC - add logs window for troubleshooting ([#2951]) + +[#2951]: https://github.com/nymtech/nym/issues/2951 + +## [nym-connect-v1.1.9](https://github.com/nymtech/nym/tree/nym-connect-v1.1.9) (2023-02-14) + +- Button animations ([#2949]) +- add effect when the button is clicked ([#2947]) +- UI to select gateways based on some performance criteria by checking gateways' routing score from nym-api ([#2942]) +- client health check when connecting ([#2859]) +- allow user to select own gateway ([#2952]) + +[#2952]: https://github.com/nymtech/nym/issues/2952 +[#2949]: https://github.com/nymtech/nym/issues/2949 +[#2947]: https://github.com/nymtech/nym/issues/2947 +[#2942]: https://github.com/nymtech/nym/issues/2942 +[#2859]: https://github.com/nymtech/nym/issues/2859 + +## [nym-connect-v1.1.8](https://github.com/nymtech/nym/tree/nym-connect-v1.1.8) (2023-01-31) + +- Add supported apps in the menu + update guide ([#2868]) +- Copy changes to remove the dropdown: ([#2777]) + +[#2868]: https://github.com/nymtech/nym/issues/2868 +[#2777]: https://github.com/nymtech/nym/issues/2777 + +## [nym-connect-v1.1.7](https://github.com/nymtech/nym/tree/nym-connect-v1.1.7) (2023-01-24) + +- Remove test and earn ([#2865]) + +[#2865]: https://github.com/nymtech/nym/issue/2865 + +## [nym-connect-v1.1.6](https://github.com/nymtech/nym/tree/nym-connect-v1.1.6) (2023-01-17) + +- part (1) show gateway status on the UI if the gateway is not live, is overloaded or is slow ([#2824]) + +[#2824]: https://github.com/nymtech/nym/pull/2824 + +## [nym-connect-v1.1.5](https://github.com/nymtech/nym/tree/nym-connect-v1.1.5) (2023-01-10) + +- get version number from tauri and display by @fmtabbara in https://github.com/nymtech/nym/pull/2684 +- Feature/nym connect experimental software text by @fmtabbara in https://github.com/nymtech/nym/pull/2692 +- NymConnect - Display service info in tooltip **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2704 + +## [nym-connect-v1.1.4](https://github.com/nymtech/nym/tree/nym-connect-v1.1.4) (2022-12-20) + +This release contains the new opt-in Test & Earn program, and it uses a stress-tested directory of network requesters to improve reliability. It also has some bugfixes, performance improvements, and better error handling. + +- nym-connect: send status messages from socks5 task to tauri backend by @octol in https://github.com/nymtech/nym/pull/1882 +- socks5: rework waiting in inbound.rs by @octol in https://github.com/nymtech/nym/pull/1880 +- Test&Earn by @mmsinclair in https://github.com/nymtech/nym/pull/2729 + +## [nym-connect-v1.1.3](https://github.com/nymtech/nym/tree/nym-connect-v1.1.3) (2022-12-13) + +- socks5-client: added support for socks4a. + +## [nym-connect-v1.1.2](https://github.com/nymtech/nym/tree/nym-connect-v1.1.2) (2022-12-06) + +- socks5-client: fix error with client failing and disconnecting unnecessarily. + +## [nym-connect-v1.1.1](https://github.com/nymtech/nym/tree/nym-connect-v1.1.1) (2022-11-29) + +- socks5-client: fix multiplex concurrent connections ([#1720], [#1777]) +- socks5-client: fix wait closing inbound connection until data is sent, and throttle incoming data in general ([#1772], [#1783],[#1789]) +- socks5-client: fix shutting down all background workers if anyone of them panics or errors out. This fixes an issue where the nym-connect UI was showing connected even though the socks5 tunnel was non-functional. ([#1805]) +- gateway-libs: fix decryping messages stored on the gateway between reconnects ([#1786]) + +- nymconnect: updated UI +- nymconnect: new help area +- nymconnect: listen for service errors and display on frontend + +[#1720]: https://github.com/nymtech/nym/pull/1720 +[#1772]: https://github.com/nymtech/nym/pull/1772 +[#1777]: https://github.com/nymtech/nym/pull/1777 +[#1783]: https://github.com/nymtech/nym/pull/1783 +[#1786]: https://github.com/nymtech/nym/pull/1786 +[#1789]: https://github.com/nymtech/nym/pull/1789 +[#1805]: https://github.com/nymtech/nym/pull/1805 + +## [nym-connect-v1.1.0](https://github.com/nymtech/nym/tree/nym-connect-v1.1.0) (2022-11-09) + +- nym-connect: rework of rewarding changes the directory data structures that describe the mixnet topology ([#1472]) +- clients: add testing-only support for two more extended packet sizes (8kb and 16kb). +- native-client/socks5-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666]) +- native-client/socks5-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664]) +- native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671]) +- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611]) +- validator-client: added `query_contract_smart` and `query_contract_raw` on `NyxdClient` ([#1558]) + +[#1472]: https://github.com/nymtech/nym/pull/1472 +[#1558]: https://github.com/nymtech/nym/pull/1558 +[#1611]: https://github.com/nymtech/nym/pull/1611 +[#1664]: https://github.com/nymtech/nym/pull/1664 +[#1666]: https://github.com/nymtech/nym/pull/1666 +[#1671]: https://github.com/nymtech/nym/pull/1671 + +## [nym-connect-v1.0.2](https://github.com/nymtech/nym/tree/nym-connect-v1.0.2) (2022-08-18) + +### Changed + +- nym-connect: "load balance" the service providers by picking a random Service Provider for each Service and storing in local storage so it remains sticky for the user ([#1540]) +- nym-connect: the ServiceProviderSelector only displays the available Services, and picks a random Service Provider for Services the user has never used before ([#1540]) +- nym-connect: add `local-forage` for storing user settings ([#1540]) + +[#1540]: https://github.com/nymtech/nym/pull/1540 + +## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22) + +### Added + +- nym-connect: initial proof-of-concept of a UI around the socks5 client was added +- nym-connect: add ability to select network requester and gateway ([#1427]) +- nym-connect: add ability to export gateway keys as JSON +- nym-connect: add auto updater + +### Changed + +- nym-connect: reuse config id instead of creating a new id on each connection + +[#1427]: https://github.com/nymtech/nym/pull/1427 diff --git a/nym-connect/Cargo.toml b/nym-connect/desktop/Cargo.toml similarity index 100% rename from nym-connect/Cargo.toml rename to nym-connect/desktop/Cargo.toml diff --git a/nym-connect/README.md b/nym-connect/desktop/README.md similarity index 99% rename from nym-connect/README.md rename to nym-connect/desktop/README.md index bbd9b55bbb..a30341be85 100644 --- a/nym-connect/README.md +++ b/nym-connect/desktop/README.md @@ -3,7 +3,7 @@ Copyright 2020 - Nym Technologies SA <contact@nymtech.net> SPDX-License-Identifier: Apache-2.0 --> -# Nym Connect +# Nym Connect - Desktop Nym is an open-source, decentralized and permissionless privacy system. It provides full-stack privacy, allowing other applications, services or blockchains to provide their users with strong metadata protection, at both the network level (mixnet), and the application level (anonymous credentials) without the need to build privacy from scratch. diff --git a/nym-connect/desktop/dist/.gitkeep b/nym-connect/desktop/dist/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/nym-connect/package.json b/nym-connect/desktop/package.json similarity index 86% rename from nym-connect/package.json rename to nym-connect/desktop/package.json index bff93c5a67..032593ab66 100644 --- a/nym-connect/package.json +++ b/nym-connect/desktop/package.json @@ -1,20 +1,20 @@ { "name": "@nym/nym-connect", - "version": "1.1.4", + "version": "1.1.12", "main": "index.js", "license": "MIT", "scripts": { - "prewebpack:dev": "yarn --cwd .. build", + "prewebpack:dev": "yarn --cwd ../.. build", "webpack:dev": "yarn webpack serve --config webpack.dev.js", "webpack:dev:onlyThis": "yarn webpack serve --config webpack.dev.js", "webpack:prod": "yarn webpack --progress --config webpack.prod.js", "tauri:dev": "RUST_DEBUG=1 yarn tauri dev", "tauri:build": "yarn tauri build", "dev": "run-p webpack:dev tauri:dev", - "prebuild": "yarn --cwd .. build", + "prebuild": "yarn --cwd ../.. build", "build": "run-s webpack:prod tauri:build", "storybook": "start-storybook -p 6006", - "prestorybook:build": "yarn --cwd .. build", + "prestorybook:build": "yarn --cwd ../.. build", "storybook:build": "build-storybook", "tsc": "tsc --noEmit true", "tsc:watch": "tsc --noEmit true --watch", @@ -30,18 +30,20 @@ "@mui/icons-material": "^5.2.0", "@mui/material": "^5.2.2", "@mui/styles": "^5.2.2", + "@mui/system": ">= 5", + "@mui/lab": "^5.0.0-alpha.72", "@nymproject/react": "^1.0.0", "@tauri-apps/api": "^1.2.0", "@tauri-apps/tauri-forage": "^1.0.0-beta.2", "clsx": "^1.1.1", "luxon": "^2.3.0", "pretty-bytes": "^6.0.0", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-error-boundary": "^3.1.3", "react-hook-form": "^7.14.2", "react-markdown": "^8.0.4", - "react-router-dom": "^5.2.0", + "react-router-dom": "^6.7.0", "semver": "^6.3.0", "yup": "^0.32.9" }, @@ -54,7 +56,7 @@ "@mdx-js/loader": "^2.1.5", "@nymproject/eslint-config-react-typescript": "^1.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", - "@storybook/react": "^6.5.8", + "@storybook/react": "^6.5.15", "@svgr/webpack": "^6.1.1", "@tauri-apps/cli": "^1.2.2", "@testing-library/jest-dom": "^5.14.1", @@ -62,16 +64,16 @@ "@types/jest": "^27.0.1", "@types/luxon": "^2.3.2", "@types/node": "^16.7.13", - "@types/react": "^17.0.34", - "@types/react-dom": "^17.0.9", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.10", "@types/semver": "^7.3.8", "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.13.0", - "babel-loader": "^8.2.2", + "babel-loader": "^8.3.0", "babel-plugin-root-import": "^6.6.0", "clean-webpack-plugin": "^4.0.0", - "css-loader": "^6.2.0", + "css-loader": "^6.7.3", "css-minimizer-webpack-plugin": "^3.0.2", "dotenv-webpack": "^7.0.3", "eslint": "^8.10.0", @@ -97,18 +99,18 @@ "prettier": "2.3.2", "react-refresh": "^0.10.0", "react-refresh-typescript": "^2.0.2", - "style-loader": "^3.2.1", + "style-loader": "^3.3.1", "thread-loader": "^3.0.4", "ts-jest": "^27.0.5", - "ts-loader": "^9.2.5", + "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^3.5.2", "typescript": "^4.6.2", "url-loader": "^4.1.1", - "webpack": "^5.64.3", + "webpack": "^5.75.0", "webpack-cli": "^4.8.0", "webpack-dev-server": "^4.5.0", "webpack-favicons": "^1.3.8", "webpack-merge": "^5.8.0", "yaml-loader": "^0.8.0" } -} +} \ No newline at end of file diff --git a/nym-connect/public/favicon.ico b/nym-connect/desktop/public/favicon.ico similarity index 100% rename from nym-connect/public/favicon.ico rename to nym-connect/desktop/public/favicon.ico diff --git a/nym-connect/public/favicon.png b/nym-connect/desktop/public/favicon.png similarity index 100% rename from nym-connect/public/favicon.png rename to nym-connect/desktop/public/favicon.png diff --git a/nym-connect/public/growth.html b/nym-connect/desktop/public/growth.html similarity index 100% rename from nym-connect/public/growth.html rename to nym-connect/desktop/public/growth.html diff --git a/nym-connect/public/index.html b/nym-connect/desktop/public/index.html similarity index 100% rename from nym-connect/public/index.html rename to nym-connect/desktop/public/index.html diff --git a/nym-connect/public/log.html b/nym-connect/desktop/public/log.html similarity index 100% rename from nym-connect/public/log.html rename to nym-connect/desktop/public/log.html diff --git a/nym-connect/src-tauri/.gitignore b/nym-connect/desktop/src-tauri/.gitignore similarity index 100% rename from nym-connect/src-tauri/.gitignore rename to nym-connect/desktop/src-tauri/.gitignore diff --git a/nym-connect/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml similarity index 69% rename from nym-connect/src-tauri/Cargo.toml rename to nym-connect/desktop/src-tauri/Cargo.toml index 1ef082ea8d..c7e509b38d 100644 --- a/nym-connect/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-connect" -version = "1.1.4" +version = "1.1.12" description = "nym-connect" authors = ["Nym Technologies SA"] license = "" @@ -21,7 +21,6 @@ tauri-macros = "^1.2.1" [dependencies] anyhow = "1.0" bip39 = "1.0" -chrono = "0.4" dirs = "4.0" eyre = "0.6.5" fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"} @@ -40,17 +39,20 @@ tap = "1.0.1" tauri = { version = "^1.2.2", features = ["clipboard-write-text", "macos-private-api", "notification-all", "shell-open", "system-tray", "updater", "window-close", "window-minimize", "window-start-dragging"] } tendermint-rpc = "0.23.0" thiserror = "1.0" -tokio = { version = "1.21.2", features = ["sync", "time"] } +time = { version = "0.3.17", features = ["local-offset"] } +tokio = { version = "1.24.1", features = ["sync", "time"] } url = "2.2" yaml-rust = "0.4" -client-core = { path = "../../clients/client-core" } -config-common = { path = "../../common/config", package = "config" } -crypto = { path = "../../common/crypto" } -logging = { path = "../../common/logging"} -nym-socks5-client = { path = "../../clients/socks5" } -task = { path = "../../common/task" } -topology = { path = "../../common/topology" } + +client-core = { path = "../../../clients/client-core" } +nym-api-requests = { path = "../../../nym-api/nym-api-requests" } +nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common"} +nym-config-common = { path = "../../../common/config", package = "nym-config" } +nym-crypto = { path = "../../../common/crypto" } +nym-bin-common = { path = "../../../common/bin-common"} +nym-socks5-client = { path = "../../../clients/socks5" } +nym-task = { path = "../../../common/task" } [dev-dependencies] ts-rs = "6.1.2" diff --git a/nym-connect/src-tauri/icons/128x128.png b/nym-connect/desktop/src-tauri/icons/128x128.png similarity index 100% rename from nym-connect/src-tauri/icons/128x128.png rename to nym-connect/desktop/src-tauri/icons/128x128.png diff --git a/nym-connect/src-tauri/icons/128x128@2x.png b/nym-connect/desktop/src-tauri/icons/128x128@2x.png similarity index 100% rename from nym-connect/src-tauri/icons/128x128@2x.png rename to nym-connect/desktop/src-tauri/icons/128x128@2x.png diff --git a/nym-connect/src-tauri/icons/32x32.png b/nym-connect/desktop/src-tauri/icons/32x32.png similarity index 100% rename from nym-connect/src-tauri/icons/32x32.png rename to nym-connect/desktop/src-tauri/icons/32x32.png diff --git a/nym-connect/src-tauri/icons/Square107x107Logo.png b/nym-connect/desktop/src-tauri/icons/Square107x107Logo.png similarity index 100% rename from nym-connect/src-tauri/icons/Square107x107Logo.png rename to nym-connect/desktop/src-tauri/icons/Square107x107Logo.png diff --git a/nym-connect/src-tauri/icons/Square142x142Logo.png b/nym-connect/desktop/src-tauri/icons/Square142x142Logo.png similarity index 100% rename from nym-connect/src-tauri/icons/Square142x142Logo.png rename to nym-connect/desktop/src-tauri/icons/Square142x142Logo.png diff --git a/nym-connect/src-tauri/icons/Square150x150Logo.png b/nym-connect/desktop/src-tauri/icons/Square150x150Logo.png similarity index 100% rename from nym-connect/src-tauri/icons/Square150x150Logo.png rename to nym-connect/desktop/src-tauri/icons/Square150x150Logo.png diff --git a/nym-connect/src-tauri/icons/Square284x284Logo.png b/nym-connect/desktop/src-tauri/icons/Square284x284Logo.png similarity index 100% rename from nym-connect/src-tauri/icons/Square284x284Logo.png rename to nym-connect/desktop/src-tauri/icons/Square284x284Logo.png diff --git a/nym-connect/src-tauri/icons/Square30x30Logo.png b/nym-connect/desktop/src-tauri/icons/Square30x30Logo.png similarity index 100% rename from nym-connect/src-tauri/icons/Square30x30Logo.png rename to nym-connect/desktop/src-tauri/icons/Square30x30Logo.png diff --git a/nym-connect/src-tauri/icons/Square310x310Logo.png b/nym-connect/desktop/src-tauri/icons/Square310x310Logo.png similarity index 100% rename from nym-connect/src-tauri/icons/Square310x310Logo.png rename to nym-connect/desktop/src-tauri/icons/Square310x310Logo.png diff --git a/nym-connect/src-tauri/icons/Square44x44Logo.png b/nym-connect/desktop/src-tauri/icons/Square44x44Logo.png similarity index 100% rename from nym-connect/src-tauri/icons/Square44x44Logo.png rename to nym-connect/desktop/src-tauri/icons/Square44x44Logo.png diff --git a/nym-connect/src-tauri/icons/Square71x71Logo.png b/nym-connect/desktop/src-tauri/icons/Square71x71Logo.png similarity index 100% rename from nym-connect/src-tauri/icons/Square71x71Logo.png rename to nym-connect/desktop/src-tauri/icons/Square71x71Logo.png diff --git a/nym-connect/src-tauri/icons/Square89x89Logo.png b/nym-connect/desktop/src-tauri/icons/Square89x89Logo.png similarity index 100% rename from nym-connect/src-tauri/icons/Square89x89Logo.png rename to nym-connect/desktop/src-tauri/icons/Square89x89Logo.png diff --git a/nym-connect/src-tauri/icons/StoreLogo.png b/nym-connect/desktop/src-tauri/icons/StoreLogo.png similarity index 100% rename from nym-connect/src-tauri/icons/StoreLogo.png rename to nym-connect/desktop/src-tauri/icons/StoreLogo.png diff --git a/nym-connect/src-tauri/icons/icon.icns b/nym-connect/desktop/src-tauri/icons/icon.icns similarity index 100% rename from nym-connect/src-tauri/icons/icon.icns rename to nym-connect/desktop/src-tauri/icons/icon.icns diff --git a/nym-connect/src-tauri/icons/icon.ico b/nym-connect/desktop/src-tauri/icons/icon.ico similarity index 100% rename from nym-connect/src-tauri/icons/icon.ico rename to nym-connect/desktop/src-tauri/icons/icon.ico diff --git a/nym-connect/src-tauri/icons/icon.png b/nym-connect/desktop/src-tauri/icons/icon.png similarity index 100% rename from nym-connect/src-tauri/icons/icon.png rename to nym-connect/desktop/src-tauri/icons/icon.png diff --git a/nym-connect/src-tauri/icons/tray_icon.ico b/nym-connect/desktop/src-tauri/icons/tray_icon.ico similarity index 100% rename from nym-connect/src-tauri/icons/tray_icon.ico rename to nym-connect/desktop/src-tauri/icons/tray_icon.ico diff --git a/nym-connect/src-tauri/icons/tray_icon.png b/nym-connect/desktop/src-tauri/icons/tray_icon.png similarity index 100% rename from nym-connect/src-tauri/icons/tray_icon.png rename to nym-connect/desktop/src-tauri/icons/tray_icon.png diff --git a/nym-connect/src-tauri/src/build.rs b/nym-connect/desktop/src-tauri/src/build.rs similarity index 100% rename from nym-connect/src-tauri/src/build.rs rename to nym-connect/desktop/src-tauri/src/build.rs diff --git a/nym-connect/src-tauri/src/config/mod.rs b/nym-connect/desktop/src-tauri/src/config/mod.rs similarity index 86% rename from nym-connect/src-tauri/src/config/mod.rs rename to nym-connect/desktop/src-tauri/src/config/mod.rs index 7d4e62f7cd..e1d3379f6b 100644 --- a/nym-connect/src-tauri/src/config/mod.rs +++ b/nym-connect/desktop/src-tauri/src/config/mod.rs @@ -3,7 +3,8 @@ use crate::{ state::State, }; use client_core::config::Config as BaseConfig; -use config_common::NymConfig; +use nym_config_common::NymConfig; +use nym_crypto::asymmetric::identity; use nym_socks5::client::config::Config as Socks5Config; use std::path::PathBuf; use std::sync::Arc; @@ -15,7 +16,7 @@ static SOCKS5_CONFIG_ID: &str = "nym-connect"; pub fn socks5_config_id_appended_with(gateway_id: &str) -> Result<String> { use std::fmt::Write as _; let mut id = SOCKS5_CONFIG_ID.to_string(); - write!(id, "-{}", gateway_id)?; + write!(id, "-{gateway_id}")?; Ok(id) } @@ -92,7 +93,7 @@ impl Config { } pub fn config_file_location(id: &str) -> Result<PathBuf> { - Socks5Config::try_default_config_file_path(Some(id)) + Socks5Config::try_default_config_file_path(id) .ok_or(BackendError::CouldNotGetConfigFilename) } } @@ -109,11 +110,7 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str ); let already_init = Config::config_file_location(&id)?.exists(); if already_init { - log::info!( - "SOCKS5 client \"{}\" was already initialised before! \ - Config information will be overwritten (but keys will be kept)!", - id - ); + log::info!("SOCKS5 client \"{id}\" was already initialised before"); } // Future proofing. This flag exists for the other clients @@ -127,21 +124,26 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str log::trace!("Creating config for id: {}", id); let mut config = Config::new(id.as_str(), &provider_address); - if let Ok(raw_validators) = std::env::var(config_common::defaults::var_names::API_VALIDATOR) { + if let Ok(raw_validators) = std::env::var(nym_config_common::defaults::var_names::NYM_API) { config .get_base_mut() - .set_custom_validator_apis(config_common::parse_validators(&raw_validators)); + .set_custom_nym_apis(nym_config_common::parse_urls(&raw_validators)); } + let chosen_gateway_id = identity::PublicKey::from_base58_string(chosen_gateway_id) + .map_err(|_| BackendError::UnableToParseGateway)?; + // Setup gateway by either registering a new one, or reusing exiting keys - let gateway = client_core::init::setup_gateway::<_, Socks5Config, _>( + let gateway = client_core::init::setup_gateway_from_config::<Socks5Config, _>( register_gateway, Some(chosen_gateway_id), config.get_base(), + // TODO: another instance where this setting should probably get used + false, ) .await?; - config.get_base_mut().with_gateway_endpoint(gateway); + config.get_base_mut().set_gateway_endpoint(gateway); config.get_socks5().save_to_file(None).tap_err(|_| { log::error!("Failed to save the config file"); diff --git a/nym-connect/src-tauri/src/error.rs b/nym-connect/desktop/src-tauri/src/error.rs similarity index 63% rename from nym-connect/src-tauri/src/error.rs rename to nym-connect/desktop/src-tauri/src/error.rs index 57fc38e1c8..11ae239f77 100644 --- a/nym-connect/src-tauri/src/error.rs +++ b/nym-connect/desktop/src-tauri/src/error.rs @@ -1,4 +1,3 @@ -use client_core::client::replies::reply_storage::fs_backend; use client_core::error::ClientCoreError; use serde::{Serialize, Serializer}; use thiserror::Error; @@ -16,12 +15,12 @@ pub enum BackendError { #[from] source: std::io::Error, }, - #[error("String formatting error: {source}")] + #[error("string formatting error: {source}")] FmtError { #[from] source: std::fmt::Error, }, - #[error("Tauri error: {source}")] + #[error("tauri error: {source}")] TauriError { #[from] source: tauri::Error, @@ -39,7 +38,7 @@ pub enum BackendError { #[error("{source}")] ClientCoreError { #[from] - source: ClientCoreError<fs_backend::Backend>, + source: ClientCoreError, }, #[error("{source}")] ApiClientError { @@ -47,28 +46,36 @@ pub enum BackendError { source: crate::operations::growth::api_client::ApiClientError, }, - #[error("Could not send disconnect signal to the SOCKS5 client")] + #[error("could not send disconnect signal to the SOCKS5 client")] CoundNotSendDisconnectSignal, - #[error("No service provider set")] + #[error("no service provider set")] NoServiceProviderSet, - #[error("No gateway provider set")] + #[error("no gateway provider set")] NoGatewaySet, - #[error("Initialization failed with a panic")] + #[error("initialization failed with a panic")] InitializationPanic, - #[error("Could not get config id before gateway is set")] + #[error("could not get config id before gateway is set")] CouldNotGetIdWithoutGateway, - #[error("Could initialize without gateway set")] + #[error("could not initialize without gateway set")] CouldNotInitWithoutGateway, - #[error("Could initialize without service provider set")] + #[error("could not initialize without service provider set")] CouldNotInitWithoutServiceProvider, - #[error("Could not get file name")] + #[error("could not get file name")] CouldNotGetFilename, - #[error("Could not get config file location")] + #[error("could not get config file location")] CouldNotGetConfigFilename, - #[error("Could not load existing gateway configuration")] + #[error("could not load existing gateway configuration")] CouldNotLoadExistingGatewayConfiguration(std::io::Error), - #[error("Unable to open a new window")] + #[error("unable to open a new window")] NewWindowError, + #[error("unable to parse the specified gateway")] + UnableToParseGateway, + + #[error("HTTP get request failed: {status_code}")] + RequestFail { + url: reqwest::Url, + status_code: reqwest::StatusCode, + }, } impl Serialize for BackendError { diff --git a/nym-connect/desktop/src-tauri/src/events.rs b/nym-connect/desktop/src-tauri/src/events.rs new file mode 100644 index 0000000000..cf277a90f9 --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/events.rs @@ -0,0 +1,71 @@ +use std::sync::Arc; + +use client_core::error::ClientCoreStatusMessage; +use nym_task::manager::TaskStatus; +use tauri::async_runtime::RwLock; + +use crate::{ + state::{GatewayConnectivity, State}, + tasks, +}; + +#[derive(Clone, serde::Serialize)] +struct Payload { + title: String, + message: String, +} + +impl Payload { + fn new(title: String, message: String) -> Self { + Self { title, message } + } +} + +pub fn emit_event(event: &str, title: &str, msg: &str, window: &tauri::Window<tauri::Wry>) { + if let Err(err) = window.emit(event, Payload::new(title.into(), msg.into())) { + log::error!("Failed to emit tauri event: {err}"); + } +} + +pub fn emit_status_event<T: ToString>(event: &str, msg: &T, window: &tauri::Window<tauri::Wry>) { + if let Err(err) = window.emit(event, Payload::new("SOCKS5 update".into(), msg.to_string())) { + log::error!("Failed to emit tauri event: {err}"); + } +} + +pub async fn handle_task_status( + task_status: &TaskStatus, + state: &Arc<RwLock<State>>, + window: &tauri::Window, +) { + match task_status { + TaskStatus::Ready => { + { + let mut state_w = state.write().await; + state_w.mark_connected(window); + } + + emit_status_event("socks5-connected-event", task_status, window); + tasks::start_connection_check(state.clone(), window.clone()); + } + } +} + +pub async fn handle_client_status_message( + client_status_message: &ClientCoreStatusMessage, + state: &Arc<RwLock<State>>, + window: &tauri::Window, +) { + // TODO: use this instead once we change on the frontend too + let _event_name = match client_status_message { + ClientCoreStatusMessage::GatewayIsSlow | ClientCoreStatusMessage::GatewayIsVerySlow => { + "socks5-gateway-status" + } + }; + + if let Ok(connectivity) = GatewayConnectivity::try_from(client_status_message) { + state.write().await.set_gateway_connectivity(connectivity); + } + + emit_status_event("socks5-status-event", client_status_message, window); +} diff --git a/nym-connect/desktop/src-tauri/src/logging.rs b/nym-connect/desktop/src-tauri/src/logging.rs new file mode 100644 index 0000000000..093452d67c --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/logging.rs @@ -0,0 +1,147 @@ +use fern::colors::{Color, ColoredLevelConfig}; +use serde::Serialize; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use std::str::FromStr; +use tauri::Manager; +use time::{format_description, OffsetDateTime}; + +fn formatted_time() -> String { + // if we fail to obtain local time according to local offset, fallback to utc + let _now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()); + + // if we're running it in the unit test, use the hardcoded value + #[cfg(test)] + let _now = OffsetDateTime::from_unix_timestamp(1666666666).unwrap(); + + // the unwraps are fine as we know this description is correct + // note: the reason for this very particular format is a very simple one + // it's what we've always been using since we copied it from the example, + // so feel free to update it to whatever + let format = + format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]").unwrap(); + _now.format(&format).unwrap() +} + +pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerError> { + let colors = ColoredLevelConfig::new() + .trace(Color::Magenta) + .debug(Color::Blue) + .info(Color::Green) + .warn(Color::Yellow) + .error(Color::Red); + let base_config = fern::Dispatch::new() + .level(global_level()) + .filter_lowlevel_external_components() + .show_operations(); + + let stdout_config = fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "{}[{}][{}] {}", + formatted_time(), + record.target(), + colors.color(record.level()), + message, + )) + }) + .chain(std::io::stdout()); + + let tauri_event_config = fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "{}[{}] {}", + formatted_time(), + record.target(), + message, + )) + }) + .chain(fern::Output::call(move |record| { + let msg = LogMessage { + message: record.args().to_string(), + level: record.level().into(), + }; + app_handle.emit_all("log://log", msg).unwrap(); + })); + + base_config + .chain(stdout_config) + .chain(tauri_event_config) + .apply() +} + +trait FernExt { + fn show_operations(self) -> Self; + fn filter_lowlevel_external_components(self) -> Self; +} + +impl FernExt for fern::Dispatch { + fn show_operations(self) -> Self { + if ::std::env::var("RUST_TRACE_OPERATIONS").is_ok() { + self.level_for("nym_connect::operations", log::LevelFilter::Trace) + } else { + self + } + } + + fn filter_lowlevel_external_components(self) -> Self { + self.level_for("hyper", log::LevelFilter::Warn) + .level_for("tokio_reactor", log::LevelFilter::Warn) + .level_for("reqwest", log::LevelFilter::Warn) + .level_for("mio", log::LevelFilter::Warn) + .level_for("want", log::LevelFilter::Warn) + .level_for("sled", log::LevelFilter::Warn) + .level_for("tungstenite", log::LevelFilter::Warn) + .level_for("tokio_tungstenite", log::LevelFilter::Warn) + .level_for("rustls", log::LevelFilter::Warn) + .level_for("tokio_util", log::LevelFilter::Warn) + } +} + +fn global_level() -> log::LevelFilter { + if let Ok(s) = ::std::env::var("RUST_LOG") { + log::LevelFilter::from_str(&s).unwrap_or(log::LevelFilter::Info) + } else { + log::LevelFilter::Info + } +} + +#[derive(Debug, Serialize, Clone)] +struct LogMessage { + message: String, + level: LogLevel, +} + +// Serialize to u16 instead of strings. +#[derive(Debug, Clone, Deserialize_repr, Serialize_repr)] +#[repr(u16)] +enum LogLevel { + Trace = 1, + Debug, + Info, + Warn, + Error, +} + +impl From<log::Level> for LogLevel { + fn from(level: log::Level) -> Self { + match level { + log::Level::Trace => LogLevel::Trace, + log::Level::Debug => LogLevel::Debug, + log::Level::Info => LogLevel::Info, + log::Level::Warn => LogLevel::Warn, + log::Level::Error => LogLevel::Error, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn log_formatting() { + let expected_chrono_formated = "[2022-10-25][02:57:46]".to_string(); + let new_time_based = formatted_time(); + assert_eq!(new_time_based, expected_chrono_formated) + } +} diff --git a/nym-connect/src-tauri/src/main.rs b/nym-connect/desktop/src-tauri/src/main.rs similarity index 88% rename from nym-connect/src-tauri/src/main.rs rename to nym-connect/desktop/src-tauri/src/main.rs index 608eca4130..76a7666406 100644 --- a/nym-connect/src-tauri/src/main.rs +++ b/nym-connect/desktop/src-tauri/src/main.rs @@ -5,17 +5,17 @@ use std::sync::Arc; -use config_common::defaults::setup_env; -use tauri::{Manager, Menu}; +use nym_config_common::defaults::setup_env; +use tauri::Manager; use tokio::sync::RwLock; -use crate::menu::AddDefaultSubmenus; use crate::menu::{create_tray_menu, tray_menu_event_handler}; use crate::state::State; use crate::window::window_toggle; mod config; mod error; +mod events; mod logging; mod menu; mod models; @@ -40,14 +40,18 @@ fn main() { .invoke_handler(tauri::generate_handler![ crate::config::get_config_file_location, crate::config::get_config_id, - crate::operations::connection::status::get_connection_status, crate::operations::connection::connect::get_gateway, crate::operations::connection::connect::get_service_provider, crate::operations::connection::connect::set_gateway, crate::operations::connection::connect::set_service_provider, crate::operations::connection::connect::start_connecting, crate::operations::connection::disconnect::start_disconnecting, + crate::operations::connection::status::get_connection_health_check_status, + crate::operations::connection::status::get_connection_status, + crate::operations::connection::status::get_gateway_connection_status, + crate::operations::connection::status::start_connection_health_check_task, crate::operations::directory::get_services, + crate::operations::directory::get_gateways_detailed, crate::operations::export::export_keys, crate::operations::window::hide_window, crate::operations::growth::test_and_earn::growth_tne_get_client_id, @@ -59,7 +63,6 @@ fn main() { crate::operations::growth::test_and_earn::growth_tne_toggle_window, crate::operations::help::log::help_log_toggle_window, ]) - .menu(Menu::os_default(&context.package_info().name).add_default_app_submenus()) .on_menu_event(|event| { if event.menu_item_id() == menu::SHOW_LOG_WINDOW { let _r = crate::operations::help::log::help_log_toggle_window( diff --git a/nym-connect/src-tauri/src/menu.rs b/nym-connect/desktop/src-tauri/src/menu.rs similarity index 89% rename from nym-connect/src-tauri/src/menu.rs rename to nym-connect/desktop/src-tauri/src/menu.rs index fb6f88fb68..0bc007838b 100644 --- a/nym-connect/src-tauri/src/menu.rs +++ b/nym-connect/desktop/src-tauri/src/menu.rs @@ -16,8 +16,7 @@ impl AddDefaultSubmenus for Menu { fn add_default_app_submenus(self) -> Self { let submenu = Submenu::new( "Help", - Menu::new() - .add_item(CustomMenuItem::new(SHOW_LOG_WINDOW, "Show logs")) + Menu::new().add_item(CustomMenuItem::new(SHOW_LOG_WINDOW, "Show logs")), ); self.add_submenu(submenu) } @@ -43,10 +42,10 @@ pub(crate) fn create_tray_menu() -> SystemTray { pub(crate) fn tray_menu_event_handler(app: &AppHandle<Wry>, event: SystemTrayEvent) { match event { SystemTrayEvent::LeftClick { position, size, .. } => { - println!("Event {:?} {:?}", position, size); + println!("Event {position:?} {size:?}"); } SystemTrayEvent::MenuItemClick { id, .. } => { - println!("Event {}", id); + println!("Event {id}"); match id.as_str() { TRAY_MENU_SHOW_HIDE => { window_toggle(app); diff --git a/nym-connect/src-tauri/src/models/mod.rs b/nym-connect/desktop/src-tauri/src/models/mod.rs similarity index 73% rename from nym-connect/src-tauri/src/models/mod.rs rename to nym-connect/desktop/src-tauri/src/models/mod.rs index cde0deed96..fb43877675 100644 --- a/nym-connect/src-tauri/src/models/mod.rs +++ b/nym-connect/desktop/src-tauri/src/models/mod.rs @@ -2,6 +2,8 @@ use core::fmt; use serde::{Deserialize, Serialize}; +use crate::state::GatewayConnectivity; + #[cfg_attr(test, derive(ts_rs::TS))] #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] pub struct ConnectResult { @@ -25,6 +27,35 @@ pub enum ConnectionStatusKind { Connecting, } +#[cfg_attr(test, derive(ts_rs::TS))] +#[cfg_attr(test, ts(rename_all = "lowercase"))] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] +#[serde(rename_all = "camelCase")] +pub enum GatewayConnectionStatusKind { + Good, + Bad, + VeryBad, +} + +impl From<GatewayConnectivity> for GatewayConnectionStatusKind { + fn from(conn: GatewayConnectivity) -> Self { + match conn { + GatewayConnectivity::Good => GatewayConnectionStatusKind::Good, + GatewayConnectivity::Bad { .. } => GatewayConnectionStatusKind::Bad, + GatewayConnectivity::VeryBad { .. } => GatewayConnectionStatusKind::VeryBad, + } + } +} + +#[cfg_attr(test, derive(ts_rs::TS))] +#[cfg_attr(test, ts(rename_all = "lowercase"))] +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)] +pub enum ConnectivityTestResult { + NotAvailable, + Success, + Fail, +} + impl fmt::Display for ConnectionStatusKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { diff --git a/nym-connect/src-tauri/src/operations/connection/connect.rs b/nym-connect/desktop/src-tauri/src/operations/connection/connect.rs similarity index 89% rename from nym-connect/src-tauri/src/operations/connection/connect.rs rename to nym-connect/desktop/src-tauri/src/operations/connection/connect.rs index 6170fec0b4..1d83748698 100644 --- a/nym-connect/src-tauri/src/operations/connection/connect.rs +++ b/nym-connect/desktop/src-tauri/src/operations/connection/connect.rs @@ -11,6 +11,7 @@ pub async fn start_connecting( state: tauri::State<'_, Arc<RwLock<State>>>, window: tauri::Window<tauri::Wry>, ) -> Result<ConnectResult> { + log::trace!("Start connecting"); let (msg_receiver, exit_status_receiver) = { let mut state_w = state.write().await; state_w.start_connecting(&window).await? @@ -18,8 +19,8 @@ pub async fn start_connecting( // Setup task for checking status let state = state.inner().clone(); - tasks::start_disconnect_listener(state, window.clone(), exit_status_receiver); - tasks::start_status_listener(window, msg_receiver); + tasks::start_disconnect_listener(state.clone(), window.clone(), exit_status_receiver); + tasks::start_status_listener(state, window.clone(), msg_receiver); Ok(ConnectResult { address: "PLACEHOLDER".to_string(), diff --git a/nym-connect/src-tauri/src/operations/connection/disconnect.rs b/nym-connect/desktop/src-tauri/src/operations/connection/disconnect.rs similarity index 91% rename from nym-connect/src-tauri/src/operations/connection/disconnect.rs rename to nym-connect/desktop/src-tauri/src/operations/connection/disconnect.rs index 2361213e40..b75bf49410 100644 --- a/nym-connect/src-tauri/src/operations/connection/disconnect.rs +++ b/nym-connect/desktop/src-tauri/src/operations/connection/disconnect.rs @@ -9,6 +9,7 @@ pub async fn start_disconnecting( state: tauri::State<'_, Arc<RwLock<State>>>, window: tauri::Window<tauri::Wry>, ) -> Result<ConnectResult> { + log::trace!("Start disconnecting"); let mut guard = state.write().await; guard.start_disconnecting(&window).await?; diff --git a/nym-connect/desktop/src-tauri/src/operations/connection/health_check.rs b/nym-connect/desktop/src-tauri/src/operations/connection/health_check.rs new file mode 100644 index 0000000000..3f401933a1 --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/operations/connection/health_check.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +static HEALTH_CHECK_URL: &str = "https://nymtech.net/.wellknown/connect/healthcheck.json"; + +#[derive(Serialize, Deserialize, Debug)] +struct ConnectionSuccess { + status: String, +} + +pub async fn run_health_check() -> bool { + log::info!("Running network health check"); + match crate::operations::http::socks5_get::<_, ConnectionSuccess>(HEALTH_CHECK_URL).await { + Ok(res) if res.status == "ok" => { + log::info!("Healthcheck success!"); + true + } + Ok(res) => { + log::error!("Healthcheck failed with status: {}", res.status); + false + } + Err(err) => { + log::error!("Healthcheck failed: {err}"); + false + } + } +} diff --git a/nym-connect/src-tauri/src/operations/connection/mod.rs b/nym-connect/desktop/src-tauri/src/operations/connection/mod.rs similarity index 70% rename from nym-connect/src-tauri/src/operations/connection/mod.rs rename to nym-connect/desktop/src-tauri/src/operations/connection/mod.rs index 8c3449909e..171d07cd83 100644 --- a/nym-connect/src-tauri/src/operations/connection/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/connection/mod.rs @@ -1,3 +1,4 @@ pub mod connect; pub mod disconnect; +pub mod health_check; pub mod status; diff --git a/nym-connect/desktop/src-tauri/src/operations/connection/status.rs b/nym-connect/desktop/src-tauri/src/operations/connection/status.rs new file mode 100644 index 0000000000..7710524fb5 --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/operations/connection/status.rs @@ -0,0 +1,44 @@ +use crate::error::Result; +use crate::tasks; +use std::sync::Arc; + +use tokio::sync::RwLock; + +use crate::models::{ConnectionStatusKind, ConnectivityTestResult, GatewayConnectionStatusKind}; +use crate::state::State; + +#[tauri::command] +pub async fn get_connection_status( + state: tauri::State<'_, Arc<RwLock<State>>>, +) -> Result<ConnectionStatusKind> { + let state = state.read().await; + Ok(state.get_status()) +} + +#[tauri::command] +pub async fn get_gateway_connection_status( + state: tauri::State<'_, Arc<RwLock<State>>>, +) -> Result<GatewayConnectionStatusKind> { + let mut state_w = state.write().await; + let gateway_connectivity = state_w.get_gateway_connectivity(); + Ok(gateway_connectivity.into()) +} + +#[tauri::command] +pub async fn get_connection_health_check_status( + state: tauri::State<'_, Arc<RwLock<State>>>, +) -> Result<ConnectivityTestResult> { + let state = state.read().await; + Ok(state.get_connectivity_test_result()) +} + +// Start a connection check task. This should return with an event within one minute, and update +// the state. +// Trying to run multiple concurrent connection checks probably works but is not supported. +#[tauri::command] +pub fn start_connection_health_check_task( + state: tauri::State<'_, Arc<RwLock<State>>>, + window: tauri::Window<tauri::Wry>, +) { + tasks::start_connection_check(state.inner().clone(), window); +} diff --git a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs new file mode 100644 index 0000000000..0e58035274 --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs @@ -0,0 +1,92 @@ +use itertools::Itertools; + +use crate::error::Result; +use crate::models::{ + DirectoryService, DirectoryServiceProvider, HarbourMasterService, PagedResult, +}; +use nym_api_requests::models::GatewayBondAnnotated; +use nym_contracts_common::types::Percent; + +static SERVICE_PROVIDER_WELLKNOWN_URL: &str = + "https://nymtech.net/.wellknown/connect/service-providers.json"; + +static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100"; + +static GATEWAYS_DETAILED_URL: &str = + "https://validator.nymtech.net/api/v1/status/gateways/detailed"; + +#[tauri::command] +pub async fn get_services() -> Result<Vec<DirectoryServiceProvider>> { + log::trace!("Fetching services"); + let services_res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL) + .await? + .json::<Vec<DirectoryService>>() + .await?; + log::trace!("Received: {:#?}", services_res); + + log::trace!("Fetching gateways"); + let gateway_res = reqwest::get(GATEWAYS_DETAILED_URL) + .await? + .json::<Vec<GatewayBondAnnotated>>() + .await?; + log::trace!("Received: {:#?}", gateway_res); + + // TODO: get paged + log::trace!("Fetching active services"); + let active_services = reqwest::get(HARBOUR_MASTER_URL) + .await? + .json::<PagedResult<HarbourMasterService>>() + .await?; + log::trace!("Active: {:#?}", active_services); + + let mut filtered: Vec<DirectoryService> = vec![]; + + for service in &services_res { + let items: _ = service + .items + .clone() + .into_iter() + .filter(|sp| { + active_services + .items + .iter() + .any(|active| active.service_provider_client_id == sp.address) + }) + .collect_vec(); + log::trace!("service = {} has {} items", service.id, items.len()); + filtered.push(DirectoryService { + id: service.id.clone(), + description: service.description.clone(), + items, + }) + } + + let perf_threshold = Percent::from_percentage_value(90).unwrap(); + + // Use only services that are active AND have a performance of >= 90% + let services_with_good_performance: Vec<DirectoryServiceProvider> = filtered + .iter_mut() + .fold(vec![], |mut acc, sp| { + acc.append(&mut sp.items); + acc + }) + .into_iter() + .filter(|sp| { + gateway_res.iter().any(|gateway| { + gateway.gateway_bond.gateway.identity_key == sp.gateway + && gateway.performance >= perf_threshold + }) + }) + .collect(); + + Ok(services_with_good_performance) +} + +#[tauri::command] +pub async fn get_gateways_detailed() -> Result<Vec<GatewayBondAnnotated>> { + let res = reqwest::get(GATEWAYS_DETAILED_URL) + .await? + .json::<Vec<GatewayBondAnnotated>>() + .await?; + Ok(res) +} diff --git a/nym-connect/src-tauri/src/operations/export.rs b/nym-connect/desktop/src-tauri/src/operations/export.rs similarity index 98% rename from nym-connect/src-tauri/src/operations/export.rs rename to nym-connect/desktop/src-tauri/src/operations/export.rs index 93c2e81bc8..1ebff1a35a 100644 --- a/nym-connect/src-tauri/src/operations/export.rs +++ b/nym-connect/desktop/src-tauri/src/operations/export.rs @@ -7,7 +7,7 @@ use crate::{ }; use client_core::client::key_manager::KeyManager; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; -use crypto::asymmetric::identity; +use nym_crypto::asymmetric::identity; pub async fn get_identity_key( state: &tauri::State<'_, Arc<RwLock<State>>>, diff --git a/nym-connect/src-tauri/src/operations/growth/api_client.rs b/nym-connect/desktop/src-tauri/src/operations/growth/api_client.rs similarity index 97% rename from nym-connect/src-tauri/src/operations/growth/api_client.rs rename to nym-connect/desktop/src-tauri/src/operations/growth/api_client.rs index a8053e1977..c069cb6a9d 100644 --- a/nym-connect/src-tauri/src/operations/growth/api_client.rs +++ b/nym-connect/desktop/src-tauri/src/operations/growth/api_client.rs @@ -33,7 +33,7 @@ impl GrowthApiClient { pub fn new(resource_base: &str) -> Self { let base_url = std::env::var("API_BASE_URL").unwrap_or_else(|_| API_BASE_URL.to_string()); GrowthApiClient { - base_url: format!("{}{}", base_url, resource_base), + base_url: format!("{base_url}{resource_base}"), } } @@ -83,6 +83,7 @@ impl GrowthApiClient { } } + // TODO: use the method in `operations::http` instead pub(crate) async fn post<REQ: Serialize + ?Sized, RESP: DeserializeOwned>( &self, url: &str, @@ -249,9 +250,7 @@ impl DailyDraws { #[allow(dead_code)] pub async fn status(&self, draw_id: &str) -> Result<DrawWithWordOfTheDay, ApiClientError> { - self.client - .get(format!("/status/{}", draw_id).as_str()) - .await + self.client.get(format!("/status/{draw_id}").as_str()).await } pub async fn enter(&self, entry: &DrawEntryPartial) -> Result<DrawEntry, ApiClientError> { diff --git a/nym-connect/src-tauri/src/operations/growth/assets.rs b/nym-connect/desktop/src-tauri/src/operations/growth/assets.rs similarity index 100% rename from nym-connect/src-tauri/src/operations/growth/assets.rs rename to nym-connect/desktop/src-tauri/src/operations/growth/assets.rs diff --git a/nym-connect/src-tauri/src/operations/growth/mod.rs b/nym-connect/desktop/src-tauri/src/operations/growth/mod.rs similarity index 100% rename from nym-connect/src-tauri/src/operations/growth/mod.rs rename to nym-connect/desktop/src-tauri/src/operations/growth/mod.rs diff --git a/nym-connect/src-tauri/src/operations/growth/test_and_earn.rs b/nym-connect/desktop/src-tauri/src/operations/growth/test_and_earn.rs similarity index 100% rename from nym-connect/src-tauri/src/operations/growth/test_and_earn.rs rename to nym-connect/desktop/src-tauri/src/operations/growth/test_and_earn.rs diff --git a/nym-connect/src-tauri/src/operations/help/log.rs b/nym-connect/desktop/src-tauri/src/operations/help/log.rs similarity index 100% rename from nym-connect/src-tauri/src/operations/help/log.rs rename to nym-connect/desktop/src-tauri/src/operations/help/log.rs diff --git a/nym-connect/src-tauri/src/operations/help/mod.rs b/nym-connect/desktop/src-tauri/src/operations/help/mod.rs similarity index 100% rename from nym-connect/src-tauri/src/operations/help/mod.rs rename to nym-connect/desktop/src-tauri/src/operations/help/mod.rs diff --git a/nym-connect/src-tauri/src/operations/help/storage.rs b/nym-connect/desktop/src-tauri/src/operations/help/storage.rs similarity index 100% rename from nym-connect/src-tauri/src/operations/help/storage.rs rename to nym-connect/desktop/src-tauri/src/operations/help/storage.rs diff --git a/nym-connect/desktop/src-tauri/src/operations/http.rs b/nym-connect/desktop/src-tauri/src/operations/http.rs new file mode 100644 index 0000000000..5cfe3e1f53 --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/operations/http.rs @@ -0,0 +1,37 @@ +use crate::error::{BackendError, Result}; +use serde::de::DeserializeOwned; +use tap::TapFallible; + +pub async fn socks5_get<U, T>(url: U) -> Result<T> +where + U: reqwest::IntoUrl + std::fmt::Display, + T: DeserializeOwned, +{ + log::info!(">>> GET {url}"); + let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?; + let client = reqwest::Client::builder() + .proxy(proxy) + .timeout(std::time::Duration::from_secs(20)) + .build()?; + + let resp = client.get(url).send().await.tap_err(|err| { + log::error!("<<< Request send error: {err}"); + })?; + + if resp.status().is_client_error() || resp.status().is_server_error() { + log::error!("<<< {}", resp.status()); + return Err(BackendError::RequestFail { + url: resp.url().clone(), + status_code: resp.status(), + }); + } + + let response_body = resp.text().await.tap_err(|err| { + log::error!("<<< Request error: {err}"); + })?; + log::info!("<<< {response_body}"); + + Ok(serde_json::from_str(&response_body).tap_err(|err| { + log::error!("<<< JSON parsing error: {err}"); + })?) +} diff --git a/nym-connect/src-tauri/src/operations/mod.rs b/nym-connect/desktop/src-tauri/src/operations/mod.rs similarity index 87% rename from nym-connect/src-tauri/src/operations/mod.rs rename to nym-connect/desktop/src-tauri/src/operations/mod.rs index 1d0fa5b211..9d7d45f236 100644 --- a/nym-connect/src-tauri/src/operations/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/mod.rs @@ -3,4 +3,5 @@ pub mod directory; pub mod export; pub mod growth; pub mod help; +pub mod http; pub mod window; diff --git a/nym-connect/src-tauri/src/operations/window/mod.rs b/nym-connect/desktop/src-tauri/src/operations/window/mod.rs similarity index 100% rename from nym-connect/src-tauri/src/operations/window/mod.rs rename to nym-connect/desktop/src-tauri/src/operations/window/mod.rs diff --git a/nym-connect/src-tauri/src/state.rs b/nym-connect/desktop/src-tauri/src/state.rs similarity index 63% rename from nym-connect/src-tauri/src/state.rs rename to nym-connect/desktop/src-tauri/src/state.rs index 9da1dab42b..6afaac0dee 100644 --- a/nym-connect/src-tauri/src/state.rs +++ b/nym-connect/desktop/src-tauri/src/state.rs @@ -1,6 +1,7 @@ use std::time::Duration; -use ::config_common::NymConfig; +use ::nym_config_common::NymConfig; +use client_core::error::ClientCoreStatusMessage; use futures::SinkExt; use tap::TapFallible; use tauri::Manager; @@ -8,17 +9,46 @@ use tauri::Manager; use nym_socks5::client::{ config::Config as Socks5Config, Socks5ControlMessage, Socks5ControlMessageSender, }; +use tokio::time::Instant; use crate::{ config::{self, socks5_config_id_appended_with}, error::{BackendError, Result}, models::{ - AppEventConnectionStatusChangedPayload, ConnectionStatusKind, + AppEventConnectionStatusChangedPayload, ConnectionStatusKind, ConnectivityTestResult, APP_EVENT_CONNECTION_STATUS_CHANGED, }, tasks::{self, ExitStatusReceiver}, }; +// The client will emit messages if the connection to the gateway is poor (or the gateway can't +// keep up with the messages we are sendind). If no messages about this has been received for a +// certain duration then we assume it's all good. +const GATEWAY_CONNECTIVITY_TIMEOUT_SECS: u64 = 20; + +#[derive(Clone, Copy)] +pub enum GatewayConnectivity { + Good, + Bad { when: Instant }, + VeryBad { when: Instant }, +} + +impl TryFrom<&ClientCoreStatusMessage> for GatewayConnectivity { + type Error = BackendError; + + fn try_from(value: &ClientCoreStatusMessage) -> Result<Self, Self::Error> { + let conn = match value { + ClientCoreStatusMessage::GatewayIsSlow => GatewayConnectivity::Bad { + when: Instant::now(), + }, + ClientCoreStatusMessage::GatewayIsVerySlow => GatewayConnectivity::VeryBad { + when: Instant::now(), + }, + }; + Ok(conn) + } +} + pub struct State { /// The current connection status status: ConnectionStatusKind, @@ -31,6 +61,14 @@ pub struct State { /// Channel that is used to send command messages to the SOCKS5 client, such as to disconnect socks5_client_sender: Option<Socks5ControlMessageSender>, + + /// The client will periodically report connectivity to the gateway it's connected to. Unless + /// we get a status message from the client we assume it's good. + gateway_connectivity: GatewayConnectivity, + + /// The latest end-to-end connectivity test result. The first test is initiated on connection + /// established. Additional tests can be triggered. + connectivity_test_result: ConnectivityTestResult, } impl State { @@ -40,10 +78,40 @@ impl State { service_provider: None, gateway: None, socks5_client_sender: None, + gateway_connectivity: GatewayConnectivity::Good, + connectivity_test_result: ConnectivityTestResult::NotAvailable, } } - #[allow(unused)] + pub fn get_gateway_connectivity(&mut self) -> GatewayConnectivity { + self.gateway_connectivity = match self.gateway_connectivity { + c @ (GatewayConnectivity::Bad { when } | GatewayConnectivity::VeryBad { when }) => { + if Instant::now() > when + Duration::from_secs(GATEWAY_CONNECTIVITY_TIMEOUT_SECS) { + GatewayConnectivity::Good + } else { + c + } + } + current => current, + }; + self.gateway_connectivity + } + + pub fn set_gateway_connectivity(&mut self, gateway_connectivity: GatewayConnectivity) { + self.gateway_connectivity = gateway_connectivity + } + + pub fn get_connectivity_test_result(&self) -> ConnectivityTestResult { + self.connectivity_test_result + } + + pub fn set_connectivity_test_result( + &mut self, + connectivity_test_result: ConnectivityTestResult, + ) { + self.connectivity_test_result = connectivity_test_result; + } + pub fn get_status(&self) -> ConnectionStatusKind { self.status.clone() } @@ -86,7 +154,7 @@ impl State { pub fn load_socks5_config(&self) -> Result<Socks5Config> { let id = self.get_config_id()?; - let config = Socks5Config::load_from_file(Some(&id)) + let config = Socks5Config::load_from_file(&id) .tap_err(|_| log::warn!("Failed to load configuration file"))?; Ok(config) } @@ -96,12 +164,12 @@ impl State { pub async fn start_connecting( &mut self, window: &tauri::Window<tauri::Wry>, - ) -> Result<(task::StatusReceiver, ExitStatusReceiver)> { + ) -> Result<(nym_task::StatusReceiver, ExitStatusReceiver)> { self.set_state(ConnectionStatusKind::Connecting, window); // Setup configuration by writing to file if let Err(err) = self.init_config().await { - log::error!("Failed to initialize: {}", err); + log::error!("Failed to initialize: {err}"); // Wait a little to give the user some rudimentary feedback that the click actually // registered. @@ -111,9 +179,7 @@ impl State { } // Kick off the main task and get the channel for controlling it - let (msg_receiver, exit_status_receiver) = self.start_nym_socks5_client()?; - self.set_state(ConnectionStatusKind::Connected, window); - Ok((msg_receiver, exit_status_receiver)) + self.start_nym_socks5_client() } /// Create a configuration file @@ -133,7 +199,9 @@ impl State { } /// Spawn a new thread running the SOCKS5 client - fn start_nym_socks5_client(&mut self) -> Result<(task::StatusReceiver, ExitStatusReceiver)> { + fn start_nym_socks5_client( + &mut self, + ) -> Result<(nym_task::StatusReceiver, ExitStatusReceiver)> { let id = self.get_config_id()?; let (control_tx, msg_rx, exit_status_rx, used_gateway) = tasks::start_nym_socks5_client(&id)?; @@ -142,9 +210,16 @@ impl State { Ok((msg_rx, exit_status_rx)) } + /// Once the SOCKS5 client is operational, the status listener would call this + pub fn mark_connected(&mut self, window: &tauri::Window<tauri::Wry>) { + log::trace!("state::mark_connected"); + self.set_state(ConnectionStatusKind::Connected, window); + } + /// Disconnect by sending a message to the SOCKS5 client thread. Once it has finished and is /// disconnected, the disconnect handler will mark it as disconnected. pub async fn start_disconnecting(&mut self, window: &tauri::Window<tauri::Wry>) -> Result<()> { + log::trace!("state::start_disconnecting"); self.set_state(ConnectionStatusKind::Disconnecting, window); // Send shutdown message diff --git a/nym-connect/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs similarity index 56% rename from nym-connect/src-tauri/src/tasks.rs rename to nym-connect/desktop/src-tauri/src/tasks.rs index ef54fddcaa..9d9ae00c92 100644 --- a/nym-connect/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -1,15 +1,24 @@ -use client_core::config::{ClientCoreConfigTrait, GatewayEndpointConfig}; +use client_core::{ + config::{ClientCoreConfigTrait, GatewayEndpointConfig}, + error::ClientCoreStatusMessage, +}; use futures::{channel::mpsc, StreamExt}; +use nym_task::manager::TaskStatus; use std::sync::Arc; use tap::TapFallible; use tokio::sync::RwLock; -use config_common::NymConfig; -#[cfg(not(feature = "coconut"))] +use nym_config_common::NymConfig; use nym_socks5::client::NymClient as Socks5NymClient; use nym_socks5::client::{config::Config as Socks5Config, Socks5ControlMessageSender}; -use crate::{error::Result, state::State}; +use crate::{ + error::Result, + events::{self, emit_event, emit_status_event}, + models::{ConnectionStatusKind, ConnectivityTestResult}, + operations, + state::State, +}; pub type ExitStatusReceiver = futures::channel::oneshot::Receiver<Socks5ExitStatusMessage>; @@ -27,12 +36,12 @@ pub fn start_nym_socks5_client( id: &str, ) -> Result<( Socks5ControlMessageSender, - task::StatusReceiver, + nym_task::StatusReceiver, ExitStatusReceiver, GatewayEndpointConfig, )> { log::info!("Loading config from file: {id}"); - let config = Socks5Config::load_from_file(Some(id)) + let config = Socks5Config::load_from_file(id) .tap_err(|_| log::warn!("Failed to load configuration file"))?; let used_gateway = config.get_base().get_gateway_endpoint().clone(); @@ -83,31 +92,69 @@ pub fn start_nym_socks5_client( )) } -#[derive(Clone, serde::Serialize)] -struct Payload { - title: String, - message: String, +pub fn start_connection_check(state: Arc<RwLock<State>>, window: tauri::Window<tauri::Wry>) { + log::debug!("Starting connection check handler"); + tokio::spawn(async move { + if state.read().await.get_status() != ConnectionStatusKind::Connected { + log::error!("SOCKS5 connection status check failed: not connected"); + return; + } + + log::info!("Running connection health check"); + if operations::connection::health_check::run_health_check().await { + state + .write() + .await + .set_connectivity_test_result(ConnectivityTestResult::Success); + emit_event( + "socks5-connection-success-event", + "SOCKS5 success", + "SOCKS5 connection health check successful", + &window, + ); + } else if state.read().await.get_status() == ConnectionStatusKind::Connected { + state + .write() + .await + .set_connectivity_test_result(ConnectivityTestResult::Fail); + log::error!("SOCKS5 connection health check failed"); + emit_event( + "socks5-connection-fail-event", + "SOCKS5 error", + "SOCKS5 connection health check failed", + &window, + ); + } else { + log::debug!("SOCKS5 connection status check cancelled: not connected"); + } + + log::debug!("Connection check handler exiting"); + }); } /// The status listener listens for non-exit status messages from the background socks5 proxy task. pub fn start_status_listener( + state: Arc<RwLock<State>>, window: tauri::Window<tauri::Wry>, - mut msg_receiver: task::StatusReceiver, + mut msg_receiver: nym_task::StatusReceiver, ) { log::info!("Starting status listener"); + tokio::spawn(async move { while let Some(msg) = msg_receiver.next().await { log::info!("SOCKS5 proxy sent status message: {}", msg); - window - .emit( - "socks5-event", - Payload { - title: "SOCKS5 update".into(), - message: msg.to_string(), - }, - ) - .unwrap(); + + if let Some(task_status) = msg.downcast_ref::<TaskStatus>() { + events::handle_task_status(task_status, &state, &window).await; + } else if let Some(client_status_message) = + msg.downcast_ref::<ClientCoreStatusMessage>() + { + events::handle_client_status_message(client_status_message, &state, &window).await; + } else { + emit_status_event("socks5-status-event", &msg, &window); + } } + log::info!("Status listener exiting"); }); } @@ -123,39 +170,30 @@ pub fn start_disconnect_listener( match exit_status_receiver.await { Ok(Socks5ExitStatusMessage::Stopped) => { log::info!("SOCKS5 task reported it has finished"); - window - .emit( - "socks5-event", - Payload { - title: "SOCKS5 finished".into(), - message: "SOCKS5 task reported it has finished".into(), - }, - ) - .unwrap(); + emit_event( + "socks5-event", + "SOCKS5 finished", + "SOCKS5 task reported it has finished", + &window, + ); } Ok(Socks5ExitStatusMessage::Failed(err)) => { - log::info!("SOCKS5 task reported error: {}", err); - window - .emit( - "socks5-event", - Payload { - title: "SOCKS5 error".into(), - message: format!("SOCKS5 failed: {}", err), - }, - ) - .unwrap(); + log::info!("SOCKS5 task reported error: {err}"); + emit_event( + "socks5-event", + "SOCKS5 error", + &format!("SOCKS5 failed: {err}"), + &window, + ); } Err(_) => { log::info!("SOCKS5 task appears to have stopped abruptly"); - window - .emit( - "socks5-event", - Payload { - title: "SOCKS5 error".into(), - message: "SOCKS5 stopped abruptly. Please try reconnecting.".into(), - }, - ) - .unwrap(); + emit_event( + "socks5-event", + "SOCKS5 error", + "SOCKS5 stopped abruptly. Please try reconnecting.", + &window, + ); } } diff --git a/nym-connect/src-tauri/src/window.rs b/nym-connect/desktop/src-tauri/src/window.rs similarity index 100% rename from nym-connect/src-tauri/src/window.rs rename to nym-connect/desktop/src-tauri/src/window.rs diff --git a/nym-connect/src-tauri/tauri.conf.json b/nym-connect/desktop/src-tauri/tauri.conf.json similarity index 87% rename from nym-connect/src-tauri/tauri.conf.json rename to nym-connect/desktop/src-tauri/tauri.conf.json index 05d22942c4..854e729827 100644 --- a/nym-connect/src-tauri/tauri.conf.json +++ b/nym-connect/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-connect", - "version": "1.1.4" + "version": "1.1.12" }, "build": { "distDir": "../dist", @@ -19,7 +19,13 @@ "active": true, "targets": "all", "identifier": "net.nymtech.connect", - "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], "resources": [], "externalBin": [], "copyright": "Copyright © 2021-2022 Nym Technologies SA", @@ -44,7 +50,9 @@ }, "updater": { "active": true, - "endpoints": ["https://nymtech.net/.wellknown/connect/updater.json"], + "endpoints": [ + "https://nymtech.net/.wellknown/connect/updater.json" + ], "dialog": true, "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENCNzQ2M0E5N0VFODE2NApSV1JrZ2U2WE9rYTNETTg1OTBKdE5uWUEra0hML2syOVUvQ2lxZmFZRzZ1T3NWbGM0eVRzUTVhVwo=" }, @@ -68,7 +76,7 @@ { "title": "NymConnect", "width": 240, - "height": 675, + "height": 480, "resizable": false, "decorations": false, "transparent": true @@ -78,4 +86,4 @@ "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" } } -} +} \ No newline at end of file diff --git a/nym-connect/src/assets/help-step-four.png b/nym-connect/desktop/src/assets/help-step-four.png similarity index 100% rename from nym-connect/src/assets/help-step-four.png rename to nym-connect/desktop/src/assets/help-step-four.png diff --git a/nym-connect/src/assets/help-step-one.png b/nym-connect/desktop/src/assets/help-step-one-legacy.png similarity index 100% rename from nym-connect/src/assets/help-step-one.png rename to nym-connect/desktop/src/assets/help-step-one-legacy.png diff --git a/nym-connect/desktop/src/assets/help-step-one.png b/nym-connect/desktop/src/assets/help-step-one.png new file mode 100644 index 0000000000..482e752286 Binary files /dev/null and b/nym-connect/desktop/src/assets/help-step-one.png differ diff --git a/nym-connect/src/assets/help-step-three.png b/nym-connect/desktop/src/assets/help-step-three.png similarity index 100% rename from nym-connect/src/assets/help-step-three.png rename to nym-connect/desktop/src/assets/help-step-three.png diff --git a/nym-connect/src/assets/help-step-two.png b/nym-connect/desktop/src/assets/help-step-two-legacy.png similarity index 100% rename from nym-connect/src/assets/help-step-two.png rename to nym-connect/desktop/src/assets/help-step-two-legacy.png diff --git a/nym-connect/desktop/src/assets/help-step-two.png b/nym-connect/desktop/src/assets/help-step-two.png new file mode 100644 index 0000000000..c6f4429d74 Binary files /dev/null and b/nym-connect/desktop/src/assets/help-step-two.png differ diff --git a/nym-connect/src/components/AppVersion.tsx b/nym-connect/desktop/src/components/AppVersion.tsx similarity index 58% rename from nym-connect/src/components/AppVersion.tsx rename to nym-connect/desktop/src/components/AppVersion.tsx index 1b8adf40aa..4ced230efa 100644 --- a/nym-connect/src/components/AppVersion.tsx +++ b/nym-connect/desktop/src/components/AppVersion.tsx @@ -1,13 +1,13 @@ -import { Typography, Box } from '@mui/material'; import React from 'react'; +import { Box } from '@mui/material'; import { useClientContext } from 'src/context/main'; export const AppVersion = () => { const { appVersion } = useClientContext(); return ( - <Box sx={{ display: 'grid', width: '100%', justifyContent: 'center' }}> - <Box fontSize="small" sx={{ mb: 4, color: 'grey.600' }}> + <Box sx={{ display: 'flex', width: '100%', justifyContent: 'center' }}> + <Box fontSize="small" sx={{ color: 'grey.600' }}> Version {appVersion} </Box> </Box> diff --git a/nym-connect/desktop/src/components/AppWindowFrame.tsx b/nym-connect/desktop/src/components/AppWindowFrame.tsx new file mode 100644 index 0000000000..90e01fffa7 --- /dev/null +++ b/nym-connect/desktop/src/components/AppWindowFrame.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import { useLocation } from 'react-router-dom'; +import { useClientContext } from 'src/context/main'; +import { CustomTitleBar } from './CustomTitleBar'; + +export const AppWindowFrame: FCWithChildren = ({ children }) => { + const location = useLocation(); + const { userDefinedGateway, setUserDefinedGateway, userDefinedSPAddress, setUserDefinedSPAddress } = + useClientContext(); + + // defined functions to be used when moving away from pages + const onBack = () => { + switch (location.pathname) { + case '/menu/settings/gateway': + return () => { + // when the user moves away from the settings page and the gateway is not valid + // set isActive to false + if (!userDefinedGateway?.gateway) { + setUserDefinedGateway((current) => ({ ...current, isActive: false })); + } + }; + case '/menu/settings/service-provider': + return () => { + // when the user moves away from the settings page and the sp is not valid + // set isActive to false + if (!userDefinedSPAddress?.address) { + setUserDefinedSPAddress((current) => ({ ...current, isActive: false })); + } + }; + default: + return undefined; + } + }; + + return ( + <Box + sx={{ + display: 'grid', + gridTemplateRows: '40px 1fr', + height: '100vh', + }} + > + <CustomTitleBar path={location.pathname} onBack={onBack()} /> + <Box style={{ padding: '16px' }}>{children}</Box> + </Box> + ); +}; diff --git a/nym-connect/src/components/ConnectionButton.tsx b/nym-connect/desktop/src/components/ConnectionButton.tsx similarity index 90% rename from nym-connect/src/components/ConnectionButton.tsx rename to nym-connect/desktop/src/components/ConnectionButton.tsx index 42903a80ea..1dd72f78df 100644 --- a/nym-connect/src/components/ConnectionButton.tsx +++ b/nym-connect/desktop/src/components/ConnectionButton.tsx @@ -1,13 +1,6 @@ import React from 'react'; import { ConnectionStatusKind } from '../types'; -const getBusyFillColor = (color: string): string => { - if (color === '#F4B02D') { - return '#21D072'; - } - return '#F4B02D'; -}; - const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => { if (isError && hover) { return '#21D072'; @@ -17,13 +10,13 @@ const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isErro } switch (status) { - case ConnectionStatusKind.disconnected: + case 'disconnected': if (hover) { return '#FFFF33'; } return '#FFE600'; - case ConnectionStatusKind.connecting: - case ConnectionStatusKind.disconnecting: + case 'connecting': + case 'disconnecting': return '#FFE600'; default: // connected @@ -36,11 +29,11 @@ const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isErro const getStatusText = (status: ConnectionStatusKind, hover: boolean): string => { switch (status) { - case ConnectionStatusKind.disconnected: + case 'disconnected': return 'Connect'; - case ConnectionStatusKind.connecting: + case 'connecting': return 'Connecting'; - case ConnectionStatusKind.disconnecting: + case 'disconnecting': return 'Connected'; default: // connected @@ -51,13 +44,13 @@ const getStatusText = (status: ConnectionStatusKind, hover: boolean): string => } }; -export const ConnectionButton: React.FC<{ +export const ConnectionButton: FCWithChildren<{ status: ConnectionStatusKind; disabled?: boolean; busy?: boolean; isError?: boolean; onClick?: (status: ConnectionStatusKind) => void; -}> = ({ status, disabled, isError, onClick, busy }) => { +}> = ({ status, disabled, isError, onClick }) => { const [hover, setHover] = React.useState<boolean>(false); const handleClick = React.useCallback(() => { @@ -76,9 +69,9 @@ export const ConnectionButton: React.FC<{ return ( <svg opacity={disabled ? 0.75 : 1} - width="208" - height="208" - viewBox="0 0 208 208" + width="200" + height="200" + viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" > @@ -96,7 +89,7 @@ export const ConnectionButton: React.FC<{ <circle cx="131" cy="131" r="64" stroke={statusFillColor} strokeWidth="2" /> </g> <circle cx="131" cy="131" r="73.5" stroke={statusFillColor} strokeOpacity="0.5" /> - {status === ConnectionStatusKind.connected && hover ? ( + {status === 'connected' && hover ? ( <path d="M120.217 119.833C120.217 117.838 121.838 116.217 123.833 116.217H128.5V114H123.833C120.613 114 118 116.613 118 119.833C118 123.053 120.613 125.667 123.833 125.667H128.5V123.45H123.833C121.838 123.45 120.217 121.828 120.217 119.833ZM127 121H136.333V118.667H127V121ZM139.5 114H134.833V116.217H139.505C141.5 116.217 143.117 117.838 143.117 119.833C143.117 121.828 141.495 123.45 139.5 123.45H134.833V125.667H139.5C142.72 125.667 145.333 123.053 145.333 119.833C145.333 116.613 142.72 114 139.5 114Z" fill="white" diff --git a/nym-connect/src/components/ConnectionStats.tsx b/nym-connect/desktop/src/components/ConnectionStats.tsx similarity index 96% rename from nym-connect/src/components/ConnectionStats.tsx rename to nym-connect/desktop/src/components/ConnectionStats.tsx index 102ab29a2c..f17833e66d 100644 --- a/nym-connect/src/components/ConnectionStats.tsx +++ b/nym-connect/desktop/src/components/ConnectionStats.tsx @@ -10,7 +10,7 @@ export interface ConnectionStatsItem { const FONT_SIZE = '14px'; -export const ConnectionStats: React.FC<{ +export const ConnectionStats: FCWithChildren<{ stats: ConnectionStatsItem[]; }> = ({ stats }) => ( <Box color="rgba(255,255,255,0.6)" width="100%" display="flex" justifyContent="space-between"> diff --git a/nym-connect/desktop/src/components/ConnectionStatus.tsx b/nym-connect/desktop/src/components/ConnectionStatus.tsx new file mode 100644 index 0000000000..0c02ff2585 --- /dev/null +++ b/nym-connect/desktop/src/components/ConnectionStatus.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { Box, CircularProgress, Tooltip, Typography } from '@mui/material'; +import { DateTime } from 'luxon'; +import { ErrorOutline, InfoOutlined } from '@mui/icons-material'; +import { ConnectionStatusKind, GatewayPerformance } from '../types'; +import { ServiceProvider } from '../types/directory'; +import { GatwayWarningInfo, ServiceProviderInfo } from './TooltipInfo'; + +const FONT_SIZE = '14px'; +const FONT_WEIGHT = '600'; +const FONT_STYLE = 'normal'; + +const ConnectionStatusContent: FCWithChildren<{ + status: ConnectionStatusKind; + serviceProvider?: ServiceProvider; + gatewayError: boolean; +}> = ({ status, serviceProvider, gatewayError }) => { + if (gatewayError) { + return ( + <Tooltip title={serviceProvider ? <GatwayWarningInfo /> : undefined}> + <Box + display="flex" + alignItems="center" + gap={0.5} + justifyContent="center" + sx={{ cursor: 'pointer' }} + color="warning.main" + > + <ErrorOutline sx={{ fontSize: 14 }} /> + <Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} fontSize={FONT_SIZE} textAlign="center"> + Gateway has issues + </Typography> + </Box> + </Tooltip> + ); + } + switch (status) { + case 'connected': + return ( + <Tooltip title={serviceProvider ? <ServiceProviderInfo serviceProvider={serviceProvider} /> : undefined}> + <Box display="flex" alignItems="center" gap={0.5} justifyContent="center" sx={{ cursor: 'pointer' }}> + <InfoOutlined sx={{ fontSize: 14 }} /> + <Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} fontSize={FONT_SIZE} textAlign="center"> + Connected to Nym Mixnet + </Typography> + </Box> + </Tooltip> + ); + case 'disconnected': + return ( + <Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} textAlign="center" fontSize={FONT_SIZE}> + Connect to the mixnet + </Typography> + ); + case 'disconnecting': + return ( + <Box display="flex" alignItems="center" justifyContent="center"> + <CircularProgress size={FONT_SIZE} color="inherit" /> + <Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE}> + Disconnecting... + </Typography> + </Box> + ); + case 'connecting': + return ( + <Box display="flex" alignItems="center" justifyContent="center"> + <CircularProgress size={FONT_SIZE} color="inherit" /> + <Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} ml={1}> + Connecting... + </Typography> + </Box> + ); + + default: + return null; + } +}; + +export const ConnectionStatus: FCWithChildren<{ + status: ConnectionStatusKind; + gatewayPerformance?: GatewayPerformance; + connectedSince?: DateTime; + serviceProvider?: ServiceProvider; +}> = ({ status, serviceProvider, gatewayPerformance }) => { + const color = status === 'connected' || status === 'disconnecting' ? '#21D072' : 'white'; + + return ( + <Box color={color} sx={{ mb: 2 }}> + <ConnectionStatusContent + status={status} + serviceProvider={serviceProvider} + gatewayError={gatewayPerformance !== 'Good'} + /> + </Box> + ); +}; diff --git a/nym-connect/src/components/ConntectionTimer.tsx b/nym-connect/desktop/src/components/ConntectionTimer.tsx similarity index 100% rename from nym-connect/src/components/ConntectionTimer.tsx rename to nym-connect/desktop/src/components/ConntectionTimer.tsx diff --git a/nym-connect/src/components/CopyToClipboard.tsx b/nym-connect/desktop/src/components/CopyToClipboard.tsx similarity index 100% rename from nym-connect/src/components/CopyToClipboard.tsx rename to nym-connect/desktop/src/components/CopyToClipboard.tsx diff --git a/nym-connect/desktop/src/components/CustomTitleBar.tsx b/nym-connect/desktop/src/components/CustomTitleBar.tsx new file mode 100644 index 0000000000..5baa05c5b0 --- /dev/null +++ b/nym-connect/desktop/src/components/CustomTitleBar.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { ArrowBack, Close, Menu, Minimize } from '@mui/icons-material'; +import { Box, IconButton, Typography } from '@mui/material'; +// TODO since the structure refactor for NC this import fails +// import { NymWordmark } from '@nymproject/react/logo/NymWordmark'; +import { appWindow } from '@tauri-apps/api/window'; +import { useNavigate } from 'react-router-dom'; + +const customTitleBarStyles = { + titlebar: { + background: '#1D2125', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + padding: '16px', + paddingBottom: '0px', + borderTopLeftRadius: '12px', + borderTopRightRadius: '12px', + }, +}; + +const CustomButton = ({ Icon, onClick }: { Icon: React.JSXElementConstructor<any>; onClick: () => void }) => ( + <IconButton size="small" onClick={onClick} sx={{ padding: 0 }}> + <Icon style={{ fontSize: 16 }} /> + </IconButton> +); + +const MenuIcon = () => { + const navigate = useNavigate(); + return <CustomButton Icon={Menu} onClick={() => navigate('/menu')} />; +}; + +const ArrowBackIcon = ({ onBack }: { onBack?: () => void }) => { + const navigate = useNavigate(); + const handleBack = () => { + onBack?.(); + navigate(-1); + }; + return <CustomButton Icon={ArrowBack} onClick={handleBack} />; +}; + +const getTitle = (path: string) => { + if (path.includes('settings')) return 'Settings'; + if (path !== '/') { + const title = path.split('/').slice(-1); + return ( + <Typography textTransform="capitalize" fontWeight={700}> + {title} + </Typography> + ); + } + + // TODO return <NymWordmark width={36} />; + return ( + <Typography fontSize="16px" fontWeight={700}> + NYM + </Typography> + ); +}; + +export const CustomTitleBar = ({ path = '/', onBack }: { path?: string; onBack?: () => void }) => ( + <Box data-tauri-drag-region style={customTitleBarStyles.titlebar}> + {/* set width to keep logo centered */} + <Box sx={{ width: '40px' }}>{path === '/' ? <MenuIcon /> : <ArrowBackIcon onBack={onBack} />}</Box> + {getTitle(path)} + <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> + <CustomButton Icon={Minimize} onClick={() => appWindow.minimize()} /> + <CustomButton Icon={Close} onClick={() => appWindow.close()} /> + </Box> + </Box> +); diff --git a/nym-connect/src/components/Error.tsx b/nym-connect/desktop/src/components/Error.tsx similarity index 100% rename from nym-connect/src/components/Error.tsx rename to nym-connect/desktop/src/components/Error.tsx diff --git a/nym-connect/src/components/ErrorFallback.tsx b/nym-connect/desktop/src/components/ErrorFallback.tsx similarity index 100% rename from nym-connect/src/components/ErrorFallback.tsx rename to nym-connect/desktop/src/components/ErrorFallback.tsx diff --git a/nym-connect/desktop/src/components/ExperimentalWarning.tsx b/nym-connect/desktop/src/components/ExperimentalWarning.tsx new file mode 100644 index 0000000000..122f741d0b --- /dev/null +++ b/nym-connect/desktop/src/components/ExperimentalWarning.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Box, Typography } from '@mui/material'; + +export const ExperimentalWarning = () => ( + <Box sx={{ color: 'grey.600' }}> + <Typography fontSize="10px" textAlign="center"> + This is experimental software. + </Typography> + <Typography fontSize="10px" textAlign="center"> + Do not rely on it for strong anonymity (yet). + </Typography> + </Box> +); diff --git a/nym-connect/src/components/Growth/TestAndEarnButtonArea.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnButtonArea.tsx similarity index 89% rename from nym-connect/src/components/Growth/TestAndEarnButtonArea.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnButtonArea.tsx index ed6219e31f..5bf8958c3f 100644 --- a/nym-connect/src/components/Growth/TestAndEarnButtonArea.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnButtonArea.tsx @@ -6,9 +6,8 @@ import Content from './content/en.yaml'; import { useClientContext } from '../../context/main'; import { useTestAndEarnContext } from './context/TestAndEarnContext'; import { NymShipyardTheme } from '../../theme'; -import { ConnectionStatusKind } from '../../types'; -export const Wrapper: React.FC<{ disabled: boolean }> = ({ disabled, children }) => { +export const Wrapper: FCWithChildren<{ disabled: boolean }> = ({ disabled, children }) => { if (disabled) { return ( <Badge badgeContent="!" color="warning"> @@ -18,13 +17,14 @@ export const Wrapper: React.FC<{ disabled: boolean }> = ({ disabled, children }) </Badge> ); } + // eslint-disable-next-line react/jsx-no-useless-fragment return <>{children}</>; }; -export const TestAndEarnButtonArea: React.FC = () => { +export const TestAndEarnButtonArea: FCWithChildren = () => { const clientContext = useClientContext(); const context = useTestAndEarnContext(); - const disabled = clientContext.connectionStatus !== ConnectionStatusKind.connected; + const disabled = clientContext.connectionStatus !== 'connected'; const pinger = React.useRef<NodeJS.Timer | null>(); const doPing = async () => { diff --git a/nym-connect/src/components/Growth/TestAndEarnCurrentDraw.stories.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnCurrentDraw.stories.tsx similarity index 100% rename from nym-connect/src/components/Growth/TestAndEarnCurrentDraw.stories.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnCurrentDraw.stories.tsx diff --git a/nym-connect/src/components/Growth/TestAndEarnCurrentDraw.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnCurrentDraw.tsx similarity index 91% rename from nym-connect/src/components/Growth/TestAndEarnCurrentDraw.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnCurrentDraw.tsx index b9b029c414..b1b2bb951a 100644 --- a/nym-connect/src/components/Growth/TestAndEarnCurrentDraw.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnCurrentDraw.tsx @@ -10,7 +10,7 @@ import { useTestAndEarnContext } from './context/TestAndEarnContext'; import { DrawEntryStatus, DrawWithWordOfTheDay } from './context/types'; import Content from './content/en.yaml'; -export const TestAndEarnCurrentDrawFuture: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => { +export const TestAndEarnCurrentDrawFuture: FCWithChildren<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => { const startsUtc = React.useMemo(() => draw && DateTime.fromISO(draw.start_utc), [draw?.start_utc]); const startsIn = React.useMemo(() => { if (draw && startsUtc) { @@ -34,7 +34,7 @@ export const TestAndEarnCurrentDrawFuture: React.FC<{ draw?: DrawWithWordOfTheDa ); }; -export const TestAndEarnCurrentDrawEnter: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => { +export const TestAndEarnCurrentDrawEnter: FCWithChildren<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => { const context = useTestAndEarnContext(); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState<string>(); @@ -72,7 +72,7 @@ export const TestAndEarnCurrentDrawEnter: React.FC<{ draw?: DrawWithWordOfTheDay ); }; -export const TestAndEarnCurrentDrawEntered: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => { +export const TestAndEarnCurrentDrawEntered: FCWithChildren<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => { if (!draw || !draw.entry) { return null; } @@ -131,7 +131,7 @@ export const TestAndEarnCurrentDrawEntered: React.FC<{ draw?: DrawWithWordOfTheD ); }; -export const TestAndEarnCurrentDraw: React.FC<{ +export const TestAndEarnCurrentDraw: FCWithChildren<{ draw?: DrawWithWordOfTheDay; sx?: SxProps; }> = ({ draw, sx }) => { @@ -151,7 +151,7 @@ export const TestAndEarnCurrentDraw: React.FC<{ if (draw && closesIn && endsUtc) { return ( - <Card sx={{ mb: 2, ...sx }} elevation={10}> + <Card sx={{ mb: 2, ...(Array.isArray(sx) ? sx : [sx]) }} elevation={10}> <CardContent> <h3> {"Today's task ends "} @@ -171,7 +171,7 @@ export const TestAndEarnCurrentDraw: React.FC<{ return null; }; -export const TestAndEarnCurrentDrawWithState: React.FC<{ +export const TestAndEarnCurrentDrawWithState: FCWithChildren<{ sx?: SxProps; }> = ({ sx }) => { const context = useTestAndEarnContext(); diff --git a/nym-connect/src/components/Growth/TestAndEarnDraws.stories.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnDraws.stories.tsx similarity index 93% rename from nym-connect/src/components/Growth/TestAndEarnDraws.stories.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnDraws.stories.tsx index 83776d8e20..c70f223420 100644 --- a/nym-connect/src/components/Growth/TestAndEarnDraws.stories.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnDraws.stories.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-pascal-case */ import * as React from 'react'; import { ComponentMeta } from '@storybook/react'; import { NymShipyardTheme } from 'src/theme'; diff --git a/nym-connect/src/components/Growth/TestAndEarnDraws.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnDraws.tsx similarity index 95% rename from nym-connect/src/components/Growth/TestAndEarnDraws.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnDraws.tsx index 6dd9b8954a..24d97b7439 100644 --- a/nym-connect/src/components/Growth/TestAndEarnDraws.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnDraws.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-no-useless-fragment */ import React from 'react'; import LoadingButton from '@mui/lab/LoadingButton'; import { @@ -39,7 +40,7 @@ const statusToColor = (status: string): 'info' | 'success' | 'warning' | undefin } }; -const StatusText: React.FC<{ entry: DrawEntry }> = ({ entry }) => { +const StatusText: FCWithChildren<{ entry: DrawEntry }> = ({ entry }) => { const context = useTestAndEarnContext(); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState<string>(); @@ -132,9 +133,9 @@ const StatusText: React.FC<{ entry: DrawEntry }> = ({ entry }) => { } }; -export const TestAndEarnDraws: React.FC<{ +export const TestAndEarnDraws: FCWithChildren<{ sx?: SxProps; -}> = ({ sx }) => { +}> = () => { const context = useTestAndEarnContext(); const draws = React.useMemo<DrawEntry[]>( @@ -181,7 +182,7 @@ export const TestAndEarnDraws: React.FC<{ ); }; -export const TestAndEarnDrawsWithState: React.FC<{ +export const TestAndEarnDrawsWithState: FCWithChildren<{ sx?: SxProps; }> = ({ sx }) => { const context = useTestAndEarnContext(); diff --git a/nym-connect/src/components/Growth/TestAndEarnEnterWalletAddress.stories.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnEnterWalletAddress.stories.tsx similarity index 100% rename from nym-connect/src/components/Growth/TestAndEarnEnterWalletAddress.stories.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnEnterWalletAddress.stories.tsx diff --git a/nym-connect/src/components/Growth/TestAndEarnEnterWalletAddress.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnEnterWalletAddress.tsx similarity index 87% rename from nym-connect/src/components/Growth/TestAndEarnEnterWalletAddress.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnEnterWalletAddress.tsx index 80d7f2de5b..60aea1d51b 100644 --- a/nym-connect/src/components/Growth/TestAndEarnEnterWalletAddress.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnEnterWalletAddress.tsx @@ -1,16 +1,16 @@ import React from 'react'; import { WalletAddressFormField } from '@nymproject/react/account/WalletAddressFormField'; import { SxProps } from '@mui/system'; -import { Paper, Stack, Button, Box } from '@mui/material'; +import { Box, Button, Paper, Stack } from '@mui/material'; import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight'; import { useTestAndEarnContext } from './context/TestAndEarnContext'; -export const TestAndEarnEnterWalletAddress: React.FC<{ +export const TestAndEarnEnterWalletAddress: FCWithChildren<{ initialValue?: string; placeholder?: string; onSubmit?: () => Promise<void> | void; sx?: SxProps; -}> = ({ initialValue, placeholder, onSubmit, sx }) => { +}> = ({ initialValue, placeholder, onSubmit }) => { const context = useTestAndEarnContext(); const [isAddressValid, setAddressIsValid] = React.useState(false); return ( diff --git a/nym-connect/src/components/Growth/TestAndEarnError.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnError.tsx similarity index 68% rename from nym-connect/src/components/Growth/TestAndEarnError.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnError.tsx index 3fcf4efb59..1ad4f94cc8 100644 --- a/nym-connect/src/components/Growth/TestAndEarnError.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnError.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Box, Button } from '@mui/material'; -export const TestAndEarnError: React.FC<{ error?: string }> = ({ error = 'An error has occurred' }) => ( +export const TestAndEarnError: FCWithChildren<{ error?: string }> = ({ error = 'An error has occurred' }) => ( <Box> <Box mb={4} fontWeight="bold"> {error} diff --git a/nym-connect/src/components/Growth/TestAndEarnPopup.stories.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnPopup.stories.tsx similarity index 92% rename from nym-connect/src/components/Growth/TestAndEarnPopup.stories.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnPopup.stories.tsx index ac38ec1d4b..7e000ca093 100644 --- a/nym-connect/src/components/Growth/TestAndEarnPopup.stories.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnPopup.stories.tsx @@ -7,13 +7,8 @@ import { TestAndEarnPopup, TestAndEarnPopupContent } from './TestAndEarnPopup'; import { TestAndEarnContextProvider } from './context/TestAndEarnContext'; import { MockProvider } from '../../context/mocks/main'; import { ConnectionStatusKind } from '../../types'; -import { TestAndEarnCurrentDraw } from './TestAndEarnCurrentDraw'; -import { TestAndEarnWinner } from './TestAndEarnWinner'; -import { TestAndEarnDraws } from './TestAndEarnDraws'; -import { TestAndEarnWinnerWalletAddress } from './TestAndEarnWinnerWalletAddress'; import { MockTestAndEarnProvider_NotRegistered, - MockTestAndEarnProvider_Registered, MockTestAndEarnProvider_RegisteredAndError, MockTestAndEarnProvider_RegisteredWithDraws, MockTestAndEarnProvider_RegisteredWithDrawsAndEntry, @@ -29,12 +24,12 @@ export default { component: TestAndEarnPopupContent, } as ComponentMeta<typeof TestAndEarnPopupContent>; -const MacOSWindow: React.FC<{ width?: string | number; height?: string | number; title?: string }> = ({ - title, - width, - height, - children, -}) => ( +const MacOSWindow: FCWithChildren<{ + width?: string | number; + height?: string | number; + title?: string; + children: React.ReactNode; +}> = ({ title, width, height, children }) => ( <Box sx={{ border: '1px solid #EEEEEE', width, height }}> <Box sx={{ background: '#EEEEEE', display: 'grid', gridTemplateColumns: 'auto auto', gridTemplateRows: 'auto' }}> <Box ml={1}> @@ -67,7 +62,7 @@ const MacOSWindow: React.FC<{ width?: string | number; height?: string | number; </Box> ); -const Wrapper: React.FC<{ text: React.ReactNode }> = ({ text }) => ( +const Wrapper: FCWithChildren<{ text: React.ReactNode }> = ({ text }) => ( <NymShipyardTheme> <Alert severity="info" sx={{ mb: 4 }}> {text} diff --git a/nym-connect/src/components/Growth/TestAndEarnPopup.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnPopup.tsx similarity index 88% rename from nym-connect/src/components/Growth/TestAndEarnPopup.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnPopup.tsx index 73e8008069..2535f2ef6f 100644 --- a/nym-connect/src/components/Growth/TestAndEarnPopup.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnPopup.tsx @@ -15,7 +15,7 @@ enum Stages { registered = 'registered', } -export const TestAndEarnPopupContent: React.FC<{ +export const TestAndEarnPopupContent: FCWithChildren<{ stage?: string; connectionStatus?: ConnectionStatusKind; error?: string; @@ -28,7 +28,7 @@ export const TestAndEarnPopupContent: React.FC<{ ); } - if (!connectionStatus || connectionStatus === ConnectionStatusKind.disconnected) { + if (!connectionStatus || connectionStatus === 'disconnected') { return ( <Box p={4}> <ContentNotAvailable /> @@ -36,7 +36,7 @@ export const TestAndEarnPopupContent: React.FC<{ ); } - if (connectionStatus === ConnectionStatusKind.connecting || connectionStatus === ConnectionStatusKind.disconnecting) { + if (connectionStatus === 'connecting' || connectionStatus === 'disconnecting') { return ( <Box p={4} justifyContent="center" alignItems="center" display="flex"> <CircularProgress /> @@ -72,12 +72,12 @@ export const TestAndEarnPopupContent: React.FC<{ } }; -export const TestAndEarnPopup: React.FC = () => { +export const TestAndEarnPopup: FCWithChildren = () => { const clientContext = useClientContext(); const context = useTestAndEarnContext(); React.useEffect(() => { - if (clientContext.connectionStatus === ConnectionStatusKind.connected) { + if (clientContext.connectionStatus === 'connected') { context.refresh(); } }, [clientContext.connectionStatus]); @@ -94,7 +94,7 @@ export const TestAndEarnPopup: React.FC = () => { return () => clearInterval(interval); }, []); - if (!context.loadedOnce && clientContext.connectionStatus === ConnectionStatusKind.connected) { + if (!context.loadedOnce && clientContext.connectionStatus === 'connected') { const message = 'Waiting for data to be transferred over the mixnet...'; return ( <Box p={4}> diff --git a/nym-connect/src/components/Growth/TestAndEarnTakePart.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnTakePart.tsx similarity index 94% rename from nym-connect/src/components/Growth/TestAndEarnTakePart.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnTakePart.tsx index c5b08975f2..b8a096acf6 100644 --- a/nym-connect/src/components/Growth/TestAndEarnTakePart.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnTakePart.tsx @@ -5,9 +5,9 @@ import { SxProps } from '@mui/system'; import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight'; import { invoke } from '@tauri-apps/api'; import { useTestAndEarnContext } from './context/TestAndEarnContext'; -import { ClientId, Registration } from './context/types'; +import { Registration } from './context/types'; -export const TestAndEarnTakePart: React.FC<{ +export const TestAndEarnTakePart: FCWithChildren<{ websiteLinkUrl: string; websiteLinkText: string; content: string; @@ -41,7 +41,7 @@ export const TestAndEarnTakePart: React.FC<{ }; return ( <> - <Stack direction="row" spacing={6} alignItems="center" sx={sx}> + <Stack direction="row" spacing={6} alignItems="center" sx={{ ...(Array.isArray(sx) ? sx : [sx]) }}> <Stack alignItems="center" direction="row"> <Checkbox onChange={(_event, checked) => setAgree(checked)} /> <Box color="primary.light" fontWeight="bold"> diff --git a/nym-connect/src/components/Growth/TestAndEarnWinner.stories.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnWinner.stories.tsx similarity index 94% rename from nym-connect/src/components/Growth/TestAndEarnWinner.stories.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnWinner.stories.tsx index ed12362122..03ed77aa53 100644 --- a/nym-connect/src/components/Growth/TestAndEarnWinner.stories.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnWinner.stories.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-pascal-case */ import * as React from 'react'; import { ComponentMeta } from '@storybook/react'; import { NymShipyardTheme } from 'src/theme'; diff --git a/nym-connect/src/components/Growth/TestAndEarnWinner.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnWinner.tsx similarity index 96% rename from nym-connect/src/components/Growth/TestAndEarnWinner.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnWinner.tsx index 600d8f6f4b..41db329a2a 100644 --- a/nym-connect/src/components/Growth/TestAndEarnWinner.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnWinner.tsx @@ -8,10 +8,10 @@ import { DrawEntry, DrawEntryStatus } from './context/types'; import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress'; import Content from './content/en.yaml'; -export const TestAndEarnWinner: React.FC<{ +export const TestAndEarnWinner: FCWithChildren<{ sx?: SxProps; entry?: DrawEntry; -}> = ({ sx, entry }) => { +}> = ({ entry }) => { const context = useTestAndEarnContext(); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState<string>(); @@ -100,7 +100,7 @@ export const TestAndEarnWinner: React.FC<{ ); }; -export const TestAndEarnWinnerWithState: React.FC<{ +export const TestAndEarnWinnerWithState: FCWithChildren<{ sx?: SxProps; }> = ({ sx }) => { const context = useTestAndEarnContext(); diff --git a/nym-connect/src/components/Growth/TestAndEarnWinnerWalletAddress.tsx b/nym-connect/desktop/src/components/Growth/TestAndEarnWinnerWalletAddress.tsx similarity index 54% rename from nym-connect/src/components/Growth/TestAndEarnWinnerWalletAddress.tsx rename to nym-connect/desktop/src/components/Growth/TestAndEarnWinnerWalletAddress.tsx index 8592b651dd..deea1049e7 100644 --- a/nym-connect/src/components/Growth/TestAndEarnWinnerWalletAddress.tsx +++ b/nym-connect/desktop/src/components/Growth/TestAndEarnWinnerWalletAddress.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { Box, Button, Card, CardContent, CardMedia, Typography } from '@mui/material'; +import { Box } from '@mui/material'; import { SxProps } from '@mui/system'; import Content from './content/TestAndEarn/WinnerEntersWalletAddress.mdx'; -export const TestAndEarnWinnerWalletAddress: React.FC<{ +export const TestAndEarnWinnerWalletAddress: FCWithChildren<{ sx?: SxProps; -}> = ({ sx }) => ( +}> = () => ( <Box> <Content /> </Box> diff --git a/nym-connect/src/components/Growth/content/TestAndEarn/Error.mdx b/nym-connect/desktop/src/components/Growth/content/TestAndEarn/Error.mdx similarity index 100% rename from nym-connect/src/components/Growth/content/TestAndEarn/Error.mdx rename to nym-connect/desktop/src/components/Growth/content/TestAndEarn/Error.mdx diff --git a/nym-connect/src/components/Growth/content/TestAndEarn/Stage0_intro.mdx b/nym-connect/desktop/src/components/Growth/content/TestAndEarn/Stage0_intro.mdx similarity index 100% rename from nym-connect/src/components/Growth/content/TestAndEarn/Stage0_intro.mdx rename to nym-connect/desktop/src/components/Growth/content/TestAndEarn/Stage0_intro.mdx diff --git a/nym-connect/src/components/Growth/content/TestAndEarn/WinnerEntersWalletAddress.mdx b/nym-connect/desktop/src/components/Growth/content/TestAndEarn/WinnerEntersWalletAddress.mdx similarity index 100% rename from nym-connect/src/components/Growth/content/TestAndEarn/WinnerEntersWalletAddress.mdx rename to nym-connect/desktop/src/components/Growth/content/TestAndEarn/WinnerEntersWalletAddress.mdx diff --git a/nym-connect/src/components/Growth/content/TestAndEarnNotAvaialble.mdx b/nym-connect/desktop/src/components/Growth/content/TestAndEarnNotAvaialble.mdx similarity index 100% rename from nym-connect/src/components/Growth/content/TestAndEarnNotAvaialble.mdx rename to nym-connect/desktop/src/components/Growth/content/TestAndEarnNotAvaialble.mdx diff --git a/nym-connect/src/components/Growth/content/assets/matrix.webp b/nym-connect/desktop/src/components/Growth/content/assets/matrix.webp similarity index 100% rename from nym-connect/src/components/Growth/content/assets/matrix.webp rename to nym-connect/desktop/src/components/Growth/content/assets/matrix.webp diff --git a/nym-connect/src/components/Growth/content/assets/winner.webp b/nym-connect/desktop/src/components/Growth/content/assets/winner.webp similarity index 100% rename from nym-connect/src/components/Growth/content/assets/winner.webp rename to nym-connect/desktop/src/components/Growth/content/assets/winner.webp diff --git a/nym-connect/src/components/Growth/content/en.yaml b/nym-connect/desktop/src/components/Growth/content/en.yaml similarity index 100% rename from nym-connect/src/components/Growth/content/en.yaml rename to nym-connect/desktop/src/components/Growth/content/en.yaml diff --git a/nym-connect/src/components/Growth/context/TestAndEarnContext.tsx b/nym-connect/desktop/src/components/Growth/context/TestAndEarnContext.tsx similarity index 97% rename from nym-connect/src/components/Growth/context/TestAndEarnContext.tsx rename to nym-connect/desktop/src/components/Growth/context/TestAndEarnContext.tsx index f70e8a407d..ddf601634f 100644 --- a/nym-connect/src/components/Growth/context/TestAndEarnContext.tsx +++ b/nym-connect/desktop/src/components/Growth/context/TestAndEarnContext.tsx @@ -4,7 +4,6 @@ import { forage } from '@tauri-apps/tauri-forage'; import { invoke } from '@tauri-apps/api'; import { ClientId, DrawEntry, Draws, Registration } from './types'; import { useClientContext } from '../../../context/main'; -import { ConnectionStatusKind } from '../../../types'; export type TTestAndEarnContext = { loadedOnce: boolean; @@ -44,7 +43,7 @@ export const TestAndEarnContext = createContext(defaultValue); const CLIENT_ID_KEY = 'tne_client_id'; const REGISTRATION_KEY = 'tne_registration'; -export const TestAndEarnContextProvider = ({ children }: { children: React.ReactNode }) => { +export const TestAndEarnContextProvider: FCWithChildren = ({ children }) => { const clientContext = useClientContext(); const [loadedOnce, setLoadedOnce] = useState(false); const [loading, setLoading] = useState(true); @@ -151,7 +150,7 @@ export const TestAndEarnContextProvider = ({ children }: { children: React.React }, []); React.useEffect(() => { - if (registration && clientContext.connectionStatus === ConnectionStatusKind.connected) { + if (registration && clientContext.connectionStatus === 'connected') { setTimeout(() => { loadDraws().catch(console.error); }, 1000 * 3); diff --git a/nym-connect/src/components/Growth/context/mocks/TestAndEarnContext.tsx b/nym-connect/desktop/src/components/Growth/context/mocks/TestAndEarnContext.tsx similarity index 90% rename from nym-connect/src/components/Growth/context/mocks/TestAndEarnContext.tsx rename to nym-connect/desktop/src/components/Growth/context/mocks/TestAndEarnContext.tsx index 215367d643..b4388ddb72 100644 --- a/nym-connect/src/components/Growth/context/mocks/TestAndEarnContext.tsx +++ b/nym-connect/desktop/src/components/Growth/context/mocks/TestAndEarnContext.tsx @@ -21,7 +21,7 @@ const mockValues_NotRegistered: TTestAndEarnContext = { ...methodDefaults, }; -export const MockTestAndEarnProvider_NotRegistered = ({ children }: { children: React.ReactNode }) => ( +export const MockTestAndEarnProvider_NotRegistered: FCWithChildren = ({ children }) => ( <TestAndEarnContext.Provider value={mockValues_NotRegistered}>{children}</TestAndEarnContext.Provider> ); @@ -43,7 +43,7 @@ const mockValues_Registered: TTestAndEarnContext = { }, }; -export const MockTestAndEarnProvider_Registered = ({ children }: { children: React.ReactNode }) => ( +export const MockTestAndEarnProvider_Registered: FCWithChildren = ({ children }) => ( <TestAndEarnContext.Provider value={mockValues_Registered}>{children}</TestAndEarnContext.Provider> ); @@ -137,7 +137,7 @@ const mockValues_RegisteredWithAllDrawsAndEntry: TTestAndEarnContext = { }, }; -export const MockTestAndEarnProvider_RegisteredWithAllDraws = ({ children }: { children: React.ReactNode }) => ( +export const MockTestAndEarnProvider_RegisteredWithAllDraws: FCWithChildren = ({ children }) => ( <TestAndEarnContext.Provider value={mockValues_RegisteredWithAllDrawsAndEntry}> {children} </TestAndEarnContext.Provider> @@ -150,7 +150,7 @@ const mockValues_RegisteredWithDrawsNoCurrent: TTestAndEarnContext = { }, }; -export const MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent = ({ children }: { children: React.ReactNode }) => ( +export const MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent: FCWithChildren = ({ children }) => ( <TestAndEarnContext.Provider value={mockValues_RegisteredWithDrawsNoCurrent}>{children}</TestAndEarnContext.Provider> ); @@ -162,7 +162,7 @@ const mockValues_RegisteredWithDraws: TTestAndEarnContext = { }, }; -export const MockTestAndEarnProvider_RegisteredWithDraws = ({ children }: { children: React.ReactNode }) => ( +export const MockTestAndEarnProvider_RegisteredWithDraws: FCWithChildren = ({ children }) => ( <TestAndEarnContext.Provider value={mockValues_RegisteredWithDraws}>{children}</TestAndEarnContext.Provider> ); @@ -177,7 +177,7 @@ const mockValues_RegisteredWithDrawsAndEntry: TTestAndEarnContext = { }, }; -export const MockTestAndEarnProvider_RegisteredWithDrawsAndEntry = ({ children }: { children: React.ReactNode }) => ( +export const MockTestAndEarnProvider_RegisteredWithDrawsAndEntry: FCWithChildren = ({ children }) => ( <TestAndEarnContext.Provider value={mockValues_RegisteredWithDrawsAndEntry}>{children}</TestAndEarnContext.Provider> ); @@ -257,6 +257,6 @@ const mockValues_RegisteredAndError: TTestAndEarnContext = { error: 'Error message text will go here', }; -export const MockTestAndEarnProvider_RegisteredAndError = ({ children }: { children: React.ReactNode }) => ( +export const MockTestAndEarnProvider_RegisteredAndError: FCWithChildren = ({ children }) => ( <TestAndEarnContext.Provider value={mockValues_RegisteredAndError}>{children}</TestAndEarnContext.Provider> ); diff --git a/nym-connect/src/components/Growth/context/types.ts b/nym-connect/desktop/src/components/Growth/context/types.ts similarity index 97% rename from nym-connect/src/components/Growth/context/types.ts rename to nym-connect/desktop/src/components/Growth/context/types.ts index ebedf20d91..c7574b282f 100644 --- a/nym-connect/src/components/Growth/context/types.ts +++ b/nym-connect/desktop/src/components/Growth/context/types.ts @@ -1,5 +1,3 @@ -import { DateTime } from 'luxon'; - export interface ClientId { client_id: string; client_id_signature: string; diff --git a/nym-connect/src/components/HelpPage.tsx b/nym-connect/desktop/src/components/HelpPage.tsx similarity index 81% rename from nym-connect/src/components/HelpPage.tsx rename to nym-connect/desktop/src/components/HelpPage.tsx index 788f7264d5..5cc7815117 100644 --- a/nym-connect/src/components/HelpPage.tsx +++ b/nym-connect/desktop/src/components/HelpPage.tsx @@ -6,24 +6,26 @@ import { StepIndicator } from './HelpPageStepIndicator'; export const HelpPage = ({ step, + totalSteps, description, img, onNext, onPrev, }: { step: number; + totalSteps: number; description: string; img: any; onNext?: () => void; onPrev?: () => void; }) => ( <Stack justifyContent="space-between" sx={{ height: '100%' }}> - <Stack gap={3}> + <Stack gap={2}> <StepIndicator step={step} /> <Typography variant="body2" color="white" fontWeight="bold"> - How to connect guide {step}/4 + How to connect guide {step}/{totalSteps} </Typography> - <Typography variant="body2" sx={{ color: 'grey.400' }}> + <Typography variant="body2" sx={{ color: 'grey.400' }} textAlign="left"> {description} </Typography> <HelpImage img={img} imageDescription="select a provider" /> diff --git a/nym-connect/src/components/HelpPageActions.tsx b/nym-connect/desktop/src/components/HelpPageActions.tsx similarity index 100% rename from nym-connect/src/components/HelpPageActions.tsx rename to nym-connect/desktop/src/components/HelpPageActions.tsx diff --git a/nym-connect/desktop/src/components/HelpPageImage.tsx b/nym-connect/desktop/src/components/HelpPageImage.tsx new file mode 100644 index 0000000000..69fa38ca31 --- /dev/null +++ b/nym-connect/desktop/src/components/HelpPageImage.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export const HelpImage = ({ img, imageDescription }: { img: string; imageDescription: string }) => ( + <img src={img} alt={imageDescription} width="100%" /> +); diff --git a/nym-connect/src/components/HelpPageStepIndicator.tsx b/nym-connect/desktop/src/components/HelpPageStepIndicator.tsx similarity index 76% rename from nym-connect/src/components/HelpPageStepIndicator.tsx rename to nym-connect/desktop/src/components/HelpPageStepIndicator.tsx index b5dff42b34..02d0b3338e 100644 --- a/nym-connect/src/components/HelpPageStepIndicator.tsx +++ b/nym-connect/desktop/src/components/HelpPageStepIndicator.tsx @@ -1,15 +1,14 @@ -import { Box } from '@mui/material'; import React from 'react'; +import { Box } from '@mui/material'; const Step = ({ highlight }: { highlight: boolean }) => ( - <Box sx={{ width: '48px', height: '1px', bgcolor: highlight ? 'nym.highlight' : 'grey.600' }} /> + <Box sx={{ width: '65px', height: '1px', bgcolor: highlight ? 'nym.highlight' : 'grey.600' }} /> ); export const StepIndicator = ({ step }: { step: number }) => ( - <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-evenly' }}> + <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <Step highlight /> <Step highlight={step >= 2} /> <Step highlight={step >= 3} /> - <Step highlight={step >= 4} /> </Box> ); diff --git a/nym-connect/src/components/InfoModal.tsx b/nym-connect/desktop/src/components/InfoModal.tsx similarity index 97% rename from nym-connect/src/components/InfoModal.tsx rename to nym-connect/desktop/src/components/InfoModal.tsx index ed628de7eb..8b1157689e 100644 --- a/nym-connect/src/components/InfoModal.tsx +++ b/nym-connect/desktop/src/components/InfoModal.tsx @@ -18,7 +18,7 @@ const styles = { const ModalTitle = ({ title, withCloseIcon }: { title: string; withCloseIcon: boolean }) => ( <Box textAlign="center" mt={withCloseIcon ? -2 : 0}> <ErrorOutline sx={{ color: 'warning.main' }} /> - <Typography variant="body2" textAlign="center" sx={{ color: 'warning.main' }}> + <Typography variant="body2" textAlign="center" sx={{ color: 'warning.main', my: 1 }}> {title} </Typography> </Box> @@ -27,7 +27,7 @@ const ModalTitle = ({ title, withCloseIcon }: { title: string; withCloseIcon: bo const ModalBody = ({ description, children }: { description: string; children?: React.ReactElement }) => ( <Box textAlign="center" mt={1}> {children} - <Typography fontSize="small" sx={{ mt: 1, overflowWrap: 'anywhere', color: 'grey.300' }}> + <Typography fontSize="small" sx={{ mt: 1, overflowWrap: 'anywhere', color: 'grey.400' }}> {description} </Typography> </Box> diff --git a/nym-connect/src/components/IpAddressAndPort.tsx b/nym-connect/desktop/src/components/IpAddressAndPort.tsx similarity index 98% rename from nym-connect/src/components/IpAddressAndPort.tsx rename to nym-connect/desktop/src/components/IpAddressAndPort.tsx index 8d7d79c1f2..4e7618ed87 100644 --- a/nym-connect/src/components/IpAddressAndPort.tsx +++ b/nym-connect/desktop/src/components/IpAddressAndPort.tsx @@ -14,7 +14,7 @@ const IpAddressAndPortContainer = styled('div')({ }, }); -export const IpAddressAndPort: React.FC<{ +export const IpAddressAndPort: FCWithChildren<{ label: string; ipAddress: string; port: number; diff --git a/nym-connect/src/components/IpAddressAndPortModal.tsx b/nym-connect/desktop/src/components/IpAddressAndPortModal.tsx similarity index 62% rename from nym-connect/src/components/IpAddressAndPortModal.tsx rename to nym-connect/desktop/src/components/IpAddressAndPortModal.tsx index 8ed6d51e20..fcb0f1dfaa 100644 --- a/nym-connect/src/components/IpAddressAndPortModal.tsx +++ b/nym-connect/desktop/src/components/IpAddressAndPortModal.tsx @@ -1,8 +1,11 @@ import React from 'react'; -import { Box, Button, Typography } from '@mui/material'; +import { Box, Typography } from '@mui/material'; import { InfoModal } from './InfoModal'; import { CopyToClipboard } from './CopyToClipboard'; +const FONT_SIZE = '12px'; +const FONT_COLOR = 'grey.400'; + export const IpAddressAndPortModal = ({ show, ipAddress, @@ -16,12 +19,15 @@ export const IpAddressAndPortModal = ({ }) => ( <InfoModal show={show} - title="Almost there" - description="Copy these values to the proxy settings in your application" - Action={<Button onClick={onClose}>Done</Button>} + title="You are half way there" + description="Check NymConnect menu for supported apps" + onClose={onClose} > <Box sx={{ mt: 1 }}> - <Typography fontSize="14px" sx={{ color: 'grey.600' }}> + <Typography fontSize={FONT_SIZE} color={FONT_COLOR} sx={{ my: 2 }}> + Paste below values in the proxy settings of your app + </Typography> + <Typography fontSize={FONT_SIZE} sx={{ color: 'grey.600' }}> Socks5 address </Typography> <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}> @@ -29,7 +35,7 @@ export const IpAddressAndPortModal = ({ <CopyToClipboard text={ipAddress} iconButton light /> </Box> - <Typography fontSize="14px" sx={{ color: 'grey.600', mt: 2 }}> + <Typography fontSize={FONT_SIZE} sx={{ color: 'grey.600', mt: 2 }}> Port </Typography> <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}> diff --git a/nym-connect/src/components/LogViewer/index.tsx b/nym-connect/desktop/src/components/LogViewer/index.tsx similarity index 100% rename from nym-connect/src/components/LogViewer/index.tsx rename to nym-connect/desktop/src/components/LogViewer/index.tsx diff --git a/nym-connect/src/components/NeedHelp.tsx b/nym-connect/desktop/src/components/NeedHelp.tsx similarity index 92% rename from nym-connect/src/components/NeedHelp.tsx rename to nym-connect/desktop/src/components/NeedHelp.tsx index ce30e0883c..12d9587a5f 100644 --- a/nym-connect/src/components/NeedHelp.tsx +++ b/nym-connect/desktop/src/components/NeedHelp.tsx @@ -4,7 +4,7 @@ import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; const HELP_URL = 'https://docs.nymtech.net'; -export const NeedHelp: React.FC = () => ( +export const NeedHelp: FCWithChildren = () => ( <Box sx={{ display: 'grid', placeItems: 'center' }}> <Button component="a" href={HELP_URL} target="_blank" sx={{ fontSize: '12px', fontWeight: '600' }} color="info"> <HelpOutlineIcon color="inherit" fontSize="inherit" fontWeight="inherit" /> diff --git a/nym-connect/desktop/src/components/PowerButton/PowerButton.tsx b/nym-connect/desktop/src/components/PowerButton/PowerButton.tsx new file mode 100644 index 0000000000..34def91f9f --- /dev/null +++ b/nym-connect/desktop/src/components/PowerButton/PowerButton.tsx @@ -0,0 +1,124 @@ +import React, { useCallback } from 'react'; +import { ConnectionStatusKind } from 'src/types'; +import './power-button.css'; + +const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => { + if (isError && hover) { + return '#21D072'; + } + if (isError) { + return '#40475C'; + } + + switch (status) { + case 'disconnected': + if (hover) { + return '#FFF'; + } + return '#BBB'; + case 'connecting': + return '#FFF'; + case 'disconnecting': + return '#FFF'; + default: + // connected + if (hover) { + return '#E43E3E'; + } + return '#21D072'; + } +}; + +export const PowerButton: FCWithChildren<{ + onClick?: (status: ConnectionStatusKind) => void; + isError?: boolean; + disabled?: boolean; + status: ConnectionStatusKind; + busy?: boolean; +}> = ({ onClick, disabled, status, isError }) => { + const [hover, setHover] = React.useState<boolean>(false); + + const handleClick = () => { + if (disabled === true) { + return; + } + if (onClick) { + onClick(status); + } + }; + + const statusFillColor = getStatusFillColor(status, hover, Boolean(isError)); + + const getClassName = useCallback(() => { + if (hover) { + switch (status) { + case 'disconnected': + return 'expand'; + default: + return 'contract'; + } + } + if (!hover) { + switch (status) { + case 'connected': + return 'expand'; + default: + return 'contract'; + } + } + return 'contract'; + }, [status, hover]); + + const buttonPulse = () => { + if (status === 'connecting' || status === 'disconnecting') return 'pulse'; + return undefined; + }; + + return ( + <svg + width="190" + height="190" + viewBox="0 0 200 200" + fill="none" + xmlns="http://www.w3.org/2000/svg" + onClick={handleClick} + style={{ cursor: disabled ? 'not-allowed' : 'pointer' }} + onMouseEnter={() => !disabled && setHover(true)} + onMouseLeave={() => !disabled && setHover(false)} + > + <g transform="translate(-30, -25) "> + <circle cx={131} cy={131} r={75} strokeWidth={4} stroke={statusFillColor} filter="url(#blur)" opacity="0.6" /> + <circle cx={131} cy={131} r={25} strokeWidth={2} stroke={statusFillColor} filter="url(#blur)" opacity="0.5" /> + <g id="Button power"> + <circle cx="131" cy="131" r="68.5" stroke={statusFillColor} strokeWidth="0.5" /> + <circle id="ring-one" className={getClassName()} cx="131" cy="131" r="73" stroke={statusFillColor} /> + <circle id="ring-two" className={getClassName()} cx="131" cy="131" r="77" stroke={statusFillColor} /> + <circle id="ring-three" className={getClassName()} cx="131" cy="131" r="81" stroke={statusFillColor} /> + <circle id="ring-four" className={getClassName()} cx="131" cy="131" r="85" stroke={statusFillColor} /> + <g id="button bg"> + <circle cx="131" cy="131" r="63" stroke={statusFillColor} strokeWidth="3" className={buttonPulse()} /> + </g> + <g id="Power icon"> + <g id="Icon"> + <g id="Group 672_2"> + <g id="power_settings_new_black_24dp (1) 1_2" clipPath="url(#clip1_944_8739)"> + <path + id="Vector_2" + d="M131 113C129.9 113 129 113.9 129 115V131C129 132.1 129.9 133 131 133C132.1 133 133 132.1 133 131V115C133 113.9 132.1 113 131 113ZM141.28 118.72C140.5 119.5 140.52 120.72 141.26 121.5C143.52 123.9 144.92 127.1 145 130.64C145.18 138.3 138.84 144.9 131.18 144.98C123.36 145.1 117 138.8 117 131C117 127.32 118.42 123.98 120.74 121.48C121.48 120.7 121.48 119.48 120.72 118.72C119.92 117.92 118.62 117.94 117.86 118.76C114.96 121.84 113.14 125.94 113 130.48C112.72 140.24 120.66 148.68 130.42 148.98C140.62 149.3 149 141.12 149 130.98C149 126.24 147.16 121.96 144.16 118.76C143.4 117.94 142.08 117.92 141.28 118.72Z" + fill={statusFillColor} + className={buttonPulse()} + /> + </g> + </g> + </g> + </g> + </g> + <defs> + <filter id="blur" width="200%" height="200%" x="-50%" y="-50%"> + <feGaussianBlur stdDeviation="12.5" /> + </filter> + </defs> + </g> + </svg> + ); +}; diff --git a/nym-connect/desktop/src/components/PowerButton/power-button.css b/nym-connect/desktop/src/components/PowerButton/power-button.css new file mode 100644 index 0000000000..a493d8df25 --- /dev/null +++ b/nym-connect/desktop/src/components/PowerButton/power-button.css @@ -0,0 +1,72 @@ +#ring-expand { + animation-name: rings-expand; + animation-duration: 0.4s; + animation-timing-function: ease-in-out; + animation-play-state: paused; +} + +#ring-one.expand { + opacity: 0.3; + transition: opacity 0.1s ease-in-out; +} + +#ring-two.expand { + opacity: 0.2; + transition: opacity 0.2s ease-in-out; +} + +#ring-three.expand { + opacity: 0.1; + transition: opacity 0.3s ease-in-out; +} + +#ring-four.expand { + opacity: 0.05; + transition: opacity 0.4s ease-in-out; +} + +#ring-one.contract { + opacity: 0; + transition: opacity 0.4s; + transition-delay: 0.1s; +} + +#ring-two.contract { + opacity: 0; + transition: opacity 0.3s; + transition-delay: 0.1s; +} + +#ring-three.contract { + opacity: 0; + transition: opacity 0.1s; + transition-delay: 0.1s; +} + +#ring-four.contract { + opacity: 0; + transition: opacity 0.1s; + transition-delay: 0.1s; +} + +circle, +path { + transition: stroke 0.5s, fill 0.5s; +} + +.pulse { + animation-name: pulse; + animation-duration: 0.5s; + animation-iteration-count: infinite; + animation-direction: alternate; +} + +@keyframes pulse { + from { + opacity: 1; + } + + to { + opacity: 0.3; + } +} diff --git a/nym-connect/src/components/ServiceProviderPopup.tsx b/nym-connect/desktop/src/components/ServiceProviderPopup.tsx similarity index 87% rename from nym-connect/src/components/ServiceProviderPopup.tsx rename to nym-connect/desktop/src/components/ServiceProviderPopup.tsx index 27ecfba127..660fee4134 100644 --- a/nym-connect/src/components/ServiceProviderPopup.tsx +++ b/nym-connect/desktop/src/components/ServiceProviderPopup.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { Autocomplete, Box, Chip, Dialog, DialogProps, TextField, Typography } from '@mui/material'; -import { ServiceProvider, Service, Services } from '../types/directory'; +import { Autocomplete, Box, Dialog, DialogProps, TextField, Typography } from '@mui/material'; +import { Service, ServiceProvider, Services } from '../types/directory'; -export const ServiceProviderPopup: React.FC< +export const ServiceProviderPopup: FCWithChildren< DialogProps & { services: Services; onServiceProviderChanged: (sp?: ServiceProvider, s?: Service) => void } > = ({ services, onServiceProviderChanged, ...dialogProps }) => { const options = services.flatMap((s) => diff --git a/nym-connect/src/components/ServiceProviderSelector.stories.tsx b/nym-connect/desktop/src/components/ServiceProviderSelector.stories.tsx similarity index 100% rename from nym-connect/src/components/ServiceProviderSelector.stories.tsx rename to nym-connect/desktop/src/components/ServiceProviderSelector.stories.tsx diff --git a/nym-connect/src/components/ServiceProviderSelector.tsx b/nym-connect/desktop/src/components/ServiceProviderSelector.tsx similarity index 95% rename from nym-connect/src/components/ServiceProviderSelector.tsx rename to nym-connect/desktop/src/components/ServiceProviderSelector.tsx index 16086aa5e1..741465dac4 100644 --- a/nym-connect/src/components/ServiceProviderSelector.tsx +++ b/nym-connect/desktop/src/components/ServiceProviderSelector.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo } from 'react'; -import { Box, CircularProgress, Stack, TextField, Tooltip, Typography, MenuItem, ListItemIcon } from '@mui/material'; -import { ServiceProvider, Service, Services } from '../types/directory'; +import { Box, CircularProgress, MenuItem, Stack, TextField, Tooltip, Typography } from '@mui/material'; +import { Service, ServiceProvider, Services } from '../types/directory'; import { useTauriEvents } from '../utils'; type ServiceWithRandomSp = { @@ -11,7 +11,7 @@ type ServiceWithRandomSp = { const defaultServiceValue = { id: '', description: '', items: [] }; -export const ServiceProviderSelector: React.FC<{ +export const ServiceProviderSelector: FCWithChildren<{ onChange?: (serviceProvider?: ServiceProvider) => void; services?: Services; currentSp?: ServiceProvider; diff --git a/nym-connect/src/components/ServiceSelector.stories.tsx b/nym-connect/desktop/src/components/ServiceSelector.stories.tsx similarity index 100% rename from nym-connect/src/components/ServiceSelector.stories.tsx rename to nym-connect/desktop/src/components/ServiceSelector.stories.tsx diff --git a/nym-connect/src/components/ServiceSelector.tsx b/nym-connect/desktop/src/components/ServiceSelector.tsx similarity index 99% rename from nym-connect/src/components/ServiceSelector.tsx rename to nym-connect/desktop/src/components/ServiceSelector.tsx index 895188cd23..5eb8361d3c 100644 --- a/nym-connect/src/components/ServiceSelector.tsx +++ b/nym-connect/desktop/src/components/ServiceSelector.tsx @@ -14,7 +14,7 @@ import { Service, ServiceProvider, Services } from '../types/directory'; import { useTauriEvents } from '../utils'; import { ServiceProviderPopup } from './ServiceProviderPopup'; -export const ServiceSelector: React.FC<{ +export const ServiceSelector: FCWithChildren<{ onChange?: (serviceProvider?: ServiceProvider) => void; services?: Services; currentSp?: ServiceProvider; diff --git a/nym-connect/desktop/src/components/TooltipInfo.tsx b/nym-connect/desktop/src/components/TooltipInfo.tsx new file mode 100644 index 0000000000..2d76c6859e --- /dev/null +++ b/nym-connect/desktop/src/components/TooltipInfo.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Divider, Stack, Typography } from '@mui/material'; +import { ServiceProvider } from 'src/types/directory'; + +export const ServiceProviderInfo = ({ serviceProvider }: { serviceProvider: ServiceProvider }) => ( + <Stack gap={1} sx={{ wordWrap: 'break-word', maxWidth: 150, p: 1 }}> + <Typography variant="body2" fontWeight="bold"> + Connection info + </Typography> + <Divider /> + <Typography variant="caption" fontWeight="bold"> + Gateway <Typography variant="caption">{serviceProvider.gateway}</Typography> + </Typography> + <Divider /> + <Typography variant="caption" fontWeight="bold"> + Service provider <Typography variant="caption">{serviceProvider.address.slice(0, 35)}...</Typography> + </Typography> + </Stack> +); + +export const GatwayWarningInfo = () => ( + <Stack gap={1} sx={{ wordWrap: 'break-word', maxWidth: 150, p: 1 }}> + <Typography variant="body2" fontWeight="bold" color="warning.main"> + Connection issue + </Typography> + <Divider /> + <Typography variant="caption">Try disconnecting and connecting again</Typography> + </Stack> +); diff --git a/nym-connect/src/config.ts b/nym-connect/desktop/src/config.ts similarity index 100% rename from nym-connect/src/config.ts rename to nym-connect/desktop/src/config.ts diff --git a/nym-connect/desktop/src/context/main.tsx b/nym-connect/desktop/src/context/main.tsx new file mode 100644 index 0000000000..b999b957a2 --- /dev/null +++ b/nym-connect/desktop/src/context/main.tsx @@ -0,0 +1,211 @@ +import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { DateTime } from 'luxon'; +import { invoke } from '@tauri-apps/api'; +import { Error } from 'src/types/error'; +import { getVersion } from '@tauri-apps/api/app'; +import { useEvents } from 'src/hooks/events'; +import { UserDefinedGateway, UserDefinedSPAddress } from 'src/types/service-provider'; +import { getItemFromStorage, setItemInStorage } from 'src/utils'; +import { ConnectionStatusKind, GatewayPerformance } from '../types'; +import { ConnectionStatsItem } from '../components/ConnectionStats'; +import { ServiceProvider } from '../types/directory'; + +const FORAGE_GATEWAY_KEY = 'nym-connect-user-gateway'; +const FORAGE_SP_KEY = 'nym-connect-user-sp'; + +type ModeType = 'light' | 'dark'; + +export type TClientContext = { + mode: ModeType; + appVersion?: string; + connectionStatus: ConnectionStatusKind; + connectionStats?: ConnectionStatsItem[]; + connectedSince?: DateTime; + error?: Error; + gatewayPerformance: GatewayPerformance; + selectedProvider?: ServiceProvider; + showInfoModal: boolean; + userDefinedGateway?: UserDefinedGateway; + userDefinedSPAddress: UserDefinedSPAddress; + serviceProviders?: ServiceProvider[]; + setMode: (mode: ModeType) => void; + clearError: () => void; + setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void; + setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void; + setConnectedSince: (connectedSince: DateTime | undefined) => void; + setShowInfoModal: (show: boolean) => void; + setSerivceProvider: () => void; + startConnecting: () => Promise<void>; + startDisconnecting: () => Promise<void>; + setUserDefinedGateway: React.Dispatch<React.SetStateAction<UserDefinedGateway>>; + setUserDefinedSPAddress: React.Dispatch<React.SetStateAction<UserDefinedSPAddress>>; +}; + +export const ClientContext = createContext({} as TClientContext); + +export const ClientContextProvider: FCWithChildren = ({ children }) => { + const [mode, setMode] = useState<ModeType>('dark'); + const [connectionStatus, setConnectionStatus] = useState<ConnectionStatusKind>(ConnectionStatusKind.connected); + const [connectionStats, setConnectionStats] = useState<ConnectionStatsItem[]>(); + const [connectedSince, setConnectedSince] = useState<DateTime>(); + const [selectedProvider, setSelectedProvider] = React.useState<ServiceProvider>(); + const [serviceProviders, setServiceProviders] = React.useState<ServiceProvider[]>(); + const [error, setError] = useState<Error>(); + const [appVersion, setAppVersion] = useState<string>(); + const [gatewayPerformance, setGatewayPerformance] = useState<GatewayPerformance>('Good'); + const [showInfoModal, setShowInfoModal] = useState(false); + const [userDefinedGateway, setUserDefinedGateway] = useState<UserDefinedGateway>({ + isActive: false, + gateway: undefined, + }); + const [userDefinedSPAddress, setUserDefinedSPAddress] = useState<UserDefinedSPAddress>({ + isActive: false, + address: undefined, + }); + + const getAppVersion = async () => { + const version = await getVersion(); + return version; + }; + + useEffect(() => { + setItemInStorage({ key: FORAGE_GATEWAY_KEY, value: userDefinedGateway }); + }, [userDefinedGateway]); + + useEffect(() => { + setItemInStorage({ key: FORAGE_SP_KEY, value: userDefinedSPAddress }); + }, [userDefinedSPAddress]); + + const initialiseApp = async () => { + const services = await invoke('get_services'); + const AppVersion = await getAppVersion(); + const storedUserDefinedGateway = await getItemFromStorage({ key: FORAGE_GATEWAY_KEY }); + const storedUserDefinedSP = await getItemFromStorage({ key: FORAGE_SP_KEY }); + + setAppVersion(AppVersion); + setServiceProviders(services as ServiceProvider[]); + + if (storedUserDefinedGateway) setUserDefinedGateway(storedUserDefinedGateway); + if (storedUserDefinedSP) setUserDefinedSPAddress(storedUserDefinedSP); + }; + + useEvents({ + onError: (e) => setError(e), + onGatewayPerformanceChange: (performance) => setGatewayPerformance(performance), + onStatusChange: (status) => setConnectionStatus(status), + }); + + useEffect(() => { + initialiseApp(); + }, []); + + useEffect(() => { + // when mounting, load the connection state (needed for the Growth window, that checks the connection state) + (async () => { + const currentStatus: ConnectionStatusKind = await invoke('get_connection_status'); + setConnectionStatus(currentStatus); + })(); + }, []); + + const startConnecting = useCallback(async () => { + try { + await invoke('start_connecting'); + } catch (e) { + setError({ title: 'Could not connect', message: e as string }); + console.log(e); + } + }, []); + + const startDisconnecting = useCallback(async () => { + try { + await invoke('start_disconnecting'); + } catch (e) { + console.log(e); + } + }, []); + + const shouldUseUserGateway = !!userDefinedGateway.gateway && userDefinedGateway.isActive; + const shouldUseUserSP = !!userDefinedSPAddress.address && userDefinedSPAddress.isActive; + + const setServiceProvider = async (newServiceProvider: ServiceProvider) => { + await invoke('set_gateway', { + gateway: shouldUseUserGateway ? userDefinedGateway.gateway : newServiceProvider.gateway, + }); + await invoke('set_service_provider', { + serviceProvider: shouldUseUserSP ? userDefinedSPAddress.address : newServiceProvider.address, + }); + }; + + const getRandomSPFromList = (services: ServiceProvider[]) => { + const randomSelection = services[Math.floor(Math.random() * services.length)]; + return randomSelection; + }; + + const buildServiceProvider = async (serviceProvider: ServiceProvider) => { + const sp = { ...serviceProvider }; + + if (shouldUseUserGateway) sp.gateway = userDefinedGateway.gateway as string; + if (shouldUseUserSP) sp.address = userDefinedSPAddress.address as string; + + return sp; + }; + + const setSerivceProvider = async () => { + if (serviceProviders) { + const randomServiceProvider = getRandomSPFromList(serviceProviders); + const withUserDefinitions = await buildServiceProvider(randomServiceProvider); + await setServiceProvider(withUserDefinitions); + setSelectedProvider(withUserDefinitions); + } + return undefined; + }; + + const clearError = () => setError(undefined); + + const contextValue = useMemo( + () => ({ + mode, + appVersion, + setMode, + error, + clearError, + connectionStatus, + setConnectionStatus, + connectionStats, + showInfoModal, + setConnectionStats, + selectedProvider, + serviceProviders, + connectedSince, + setConnectedSince, + setSerivceProvider, + startConnecting, + startDisconnecting, + gatewayPerformance, + setShowInfoModal, + userDefinedSPAddress, + userDefinedGateway, + setUserDefinedGateway, + setUserDefinedSPAddress, + }), + [ + mode, + appVersion, + error, + showInfoModal, + serviceProviders, + connectedSince, + connectionStatus, + connectionStats, + connectedSince, + gatewayPerformance, + selectedProvider, + userDefinedGateway, + userDefinedSPAddress, + ], + ); + + return <ClientContext.Provider value={contextValue}>{children}</ClientContext.Provider>; +}; + +export const useClientContext = () => useContext(ClientContext); diff --git a/nym-connect/desktop/src/context/mocks/main.tsx b/nym-connect/desktop/src/context/mocks/main.tsx new file mode 100644 index 0000000000..78028b03f8 --- /dev/null +++ b/nym-connect/desktop/src/context/mocks/main.tsx @@ -0,0 +1,33 @@ +import React, { useMemo } from 'react'; +import { ConnectionStatusKind } from 'src/types'; +import { ClientContext, TClientContext } from '../main'; + +const mockValues: TClientContext = { + appVersion: 'v1.x.x', + mode: 'dark', + connectionStatus: ConnectionStatusKind.disconnected, + selectedProvider: { id: '1', description: 'Keybase service provider', gateway: 'abc123', address: '123abc' }, + gatewayPerformance: 'Good', + showInfoModal: false, + userDefinedGateway: { isActive: false, gateway: '' }, + userDefinedSPAddress: { isActive: false, address: '' }, + setShowInfoModal: () => {}, + setMode: () => {}, + clearError: () => {}, + setConnectedSince: () => {}, + setConnectionStats: () => {}, + setConnectionStatus: () => {}, + startConnecting: async () => {}, + startDisconnecting: async () => {}, + setSerivceProvider: () => {}, + setUserDefinedGateway: () => {}, + setUserDefinedSPAddress: () => {}, +}; + +export const MockProvider: FCWithChildren<{ + children?: React.ReactNode; + connectionStatus?: ConnectionStatusKind; +}> = ({ connectionStatus = ConnectionStatusKind.disconnected, children }) => { + const value = useMemo(() => ({ ...mockValues, connectionStatus }), [connectionStatus]); + return <ClientContext.Provider value={value}>{children}</ClientContext.Provider>; +}; diff --git a/nym-connect/src/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf b/nym-connect/desktop/src/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf similarity index 100% rename from nym-connect/src/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf rename to nym-connect/desktop/src/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf diff --git a/nym-connect/src/fonts/OpenSans-VariableFont_wdth,wght.ttf b/nym-connect/desktop/src/fonts/OpenSans-VariableFont_wdth,wght.ttf similarity index 100% rename from nym-connect/src/fonts/OpenSans-VariableFont_wdth,wght.ttf rename to nym-connect/desktop/src/fonts/OpenSans-VariableFont_wdth,wght.ttf diff --git a/nym-connect/src/fonts/fonts.css b/nym-connect/desktop/src/fonts/fonts.css similarity index 100% rename from nym-connect/src/fonts/fonts.css rename to nym-connect/desktop/src/fonts/fonts.css diff --git a/nym-connect/src/growth.tsx b/nym-connect/desktop/src/growth.tsx similarity index 100% rename from nym-connect/src/growth.tsx rename to nym-connect/desktop/src/growth.tsx diff --git a/nym-connect/desktop/src/hooks/events.ts b/nym-connect/desktop/src/hooks/events.ts new file mode 100644 index 0000000000..4b2c4fcc55 --- /dev/null +++ b/nym-connect/desktop/src/hooks/events.ts @@ -0,0 +1,68 @@ +import { useEffect, useRef } from 'react'; +import { listen, UnlistenFn } from '@tauri-apps/api/event'; +import { ConnectionStatusKind, GatewayPerformance } from 'src/types'; +import { Error } from 'src/types/error'; +import { TauriEvent } from 'src/types/event'; + +const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed'; + +export const useEvents = ({ + onError, + onStatusChange, + onGatewayPerformanceChange, +}: { + onError: (error: Error) => void; + onStatusChange: (status: ConnectionStatusKind) => void; + onGatewayPerformanceChange: (status: GatewayPerformance) => void; +}) => { + const timerId = useRef<NodeJS.Timeout>(); + + useEffect(() => { + const unlisten: UnlistenFn[] = []; + + // TODO: fix typings + listen(TAURI_EVENT_STATUS_CHANGED, (event) => { + const { status } = event.payload as any; + console.log(TAURI_EVENT_STATUS_CHANGED, { status, event }); + onStatusChange(status); + }) + .then((result) => { + unlisten.push(result); + }) + .catch((e) => console.log(e)); + + listen('socks5-event', (e: TauriEvent) => { + console.log(e); + onError(e.payload); + }).then((result) => { + unlisten.push(result); + }); + + listen('socks5-status-event', (e: TauriEvent) => { + if (e.payload.message.includes('slow')) { + onGatewayPerformanceChange('Poor'); + + if (timerId?.current) { + clearTimeout(timerId.current); + } + + timerId.current = setTimeout(() => { + onGatewayPerformanceChange('Good'); + }, 10000); + } + }).then((result) => { + unlisten.push(result); + }); + + listen('socks5-connection-fail-event', (e: TauriEvent) => { + onError({ title: 'Connection failed', message: `${e.payload.message} - Please disconnect and reconnect.` }); + onGatewayPerformanceChange('Poor'); + }).then((result) => { + unlisten.push(result); + }); + + return () => { + unlisten.forEach((unsubscribe) => unsubscribe()); + }; + }, []); +}; diff --git a/nym-connect/desktop/src/index.tsx b/nym-connect/desktop/src/index.tsx new file mode 100644 index 0000000000..4399c91cf2 --- /dev/null +++ b/nym-connect/desktop/src/index.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { ErrorBoundary } from 'react-error-boundary'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { GlobalStyles } from '@mui/material'; +import { ClientContextProvider } from './context/main'; +import { ErrorFallback } from './components/Error'; +import { NymMixnetTheme } from './theme'; +import { AppWindowFrame } from './components/AppWindowFrame'; +import { TestAndEarnContextProvider } from './components/Growth/context/TestAndEarnContext'; +import { AppRoutes } from './routes'; + +const elem = document.getElementById('root'); + +if (elem) { + const root = createRoot(elem); + root.render( + <ErrorBoundary FallbackComponent={ErrorFallback}> + <Router> + <ClientContextProvider> + <TestAndEarnContextProvider> + <GlobalStyles styles={{ html: { borderRadius: 10 } }} /> + <NymMixnetTheme mode="dark"> + <AppWindowFrame> + <AppRoutes /> + </AppWindowFrame> + </NymMixnetTheme> + </TestAndEarnContextProvider> + </ClientContextProvider> + </Router> + </ErrorBoundary>, + ); +} diff --git a/nym-connect/desktop/src/layouts/ConnectionLayout.tsx b/nym-connect/desktop/src/layouts/ConnectionLayout.tsx new file mode 100644 index 0000000000..d37c7ec888 --- /dev/null +++ b/nym-connect/desktop/src/layouts/ConnectionLayout.tsx @@ -0,0 +1,30 @@ +import { Box } from '@mui/material'; +import React from 'react'; + +const layout = { + display: 'grid', + gridTemplateColumns: '1fr', + gridTemplateRows: '80px 180px 1fr', + gridColumnGap: '0px', + gridRowGap: '4px', + overflow: 'hidden', + height: '100%', +}; + +export const ConnectionLayout = ({ + TopContent, + ConnectButton, + BottomContent, +}: { + TopContent: React.ReactNode; + ConnectButton: React.ReactNode; + BottomContent: React.ReactNode; +}) => ( + <Box sx={layout}> + {TopContent} + <Box display="flex" justifyContent="center" alignItems="center"> + {ConnectButton} + </Box> + {BottomContent} + </Box> +); diff --git a/nym-connect/src/log.tsx b/nym-connect/desktop/src/log.tsx similarity index 100% rename from nym-connect/src/log.tsx rename to nym-connect/desktop/src/log.tsx diff --git a/nym-connect/desktop/src/pages/connection/Connected.tsx b/nym-connect/desktop/src/pages/connection/Connected.tsx new file mode 100644 index 0000000000..5e06b8828e --- /dev/null +++ b/nym-connect/desktop/src/pages/connection/Connected.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { Box, Stack } from '@mui/material'; +import { DateTime } from 'luxon'; +import { IpAddressAndPortModal } from 'src/components/IpAddressAndPortModal'; +import { ConnectionTimer } from 'src/components/ConntectionTimer'; +import { ConnectionStatus } from 'src/components/ConnectionStatus'; +import { ConnectionStatusKind, GatewayPerformance } from 'src/types'; +import { ConnectionStatsItem } from 'src/components/ConnectionStats'; +import { IpAddressAndPort } from 'src/components/IpAddressAndPort'; +import { ServiceProvider } from 'src/types/directory'; +import { ExperimentalWarning } from 'src/components/ExperimentalWarning'; +import { ConnectionLayout } from 'src/layouts/ConnectionLayout'; +import { PowerButton } from 'src/components/PowerButton/PowerButton'; +import { Error } from 'src/types/error'; +import { InfoModal } from 'src/components/InfoModal'; + +export const Connected: FCWithChildren<{ + error?: Error; + status: ConnectionStatusKind; + showInfoModal: boolean; + gatewayPerformance: GatewayPerformance; + stats: ConnectionStatsItem[]; + ipAddress: string; + port: number; + connectedSince?: DateTime; + busy?: boolean; + isError?: boolean; + serviceProvider?: ServiceProvider; + clearError: () => void; + onConnectClick: (status: ConnectionStatusKind) => void; + closeInfoModal: () => void; +}> = ({ + error, + status, + showInfoModal, + gatewayPerformance, + ipAddress, + port, + connectedSince, + busy, + isError, + serviceProvider, + clearError, + onConnectClick, + closeInfoModal, +}) => ( + <> + {error && <InfoModal show title={error.title} description={error.message} onClose={clearError} />} + <IpAddressAndPortModal show={showInfoModal} onClose={closeInfoModal} ipAddress={ipAddress} port={port} /> + <ConnectionLayout + TopContent={ + <Box> + <ConnectionStatus + status={ConnectionStatusKind.connected} + gatewayPerformance={gatewayPerformance} + serviceProvider={serviceProvider} + /> + <ConnectionTimer connectedSince={connectedSince} /> + </Box> + } + ConnectButton={ + <PowerButton + status={status} + busy={busy} + onClick={onConnectClick} + isError={isError} + disabled={status === 'disconnecting'} + /> + } + BottomContent={ + <Stack justifyContent="space-between"> + <Box sx={{ mb: 2 }}> + <IpAddressAndPort label="Socks5 address" ipAddress={ipAddress} port={port} /> + </Box> + <ExperimentalWarning /> + </Stack> + } + /> + </> +); diff --git a/nym-connect/desktop/src/pages/connection/Disconnected.tsx b/nym-connect/desktop/src/pages/connection/Disconnected.tsx new file mode 100644 index 0000000000..127a245d4e --- /dev/null +++ b/nym-connect/desktop/src/pages/connection/Disconnected.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Stack, Typography } from '@mui/material'; +import { ConnectionStatus } from 'src/components/ConnectionStatus'; +import { ConnectionTimer } from 'src/components/ConntectionTimer'; +import { InfoModal } from 'src/components/InfoModal'; +import { Error } from 'src/types/error'; +import { ExperimentalWarning } from 'src/components/ExperimentalWarning'; +import { ServiceProvider, Services } from 'src/types/directory'; +import { ConnectionStatusKind } from 'src/types'; +import { PowerButton } from 'src/components/PowerButton/PowerButton'; +import { Box } from '@mui/system'; +import { ConnectionLayout } from 'src/layouts/ConnectionLayout'; + +export const Disconnected: FCWithChildren<{ + error?: Error; + status: ConnectionStatusKind; + services?: Services; + busy?: boolean; + isError?: boolean; + serviceProvider?: ServiceProvider; + clearError: () => void; + onConnectClick: (status: ConnectionStatusKind) => void; +}> = ({ status, error, onConnectClick, clearError }) => ( + <> + {error && <InfoModal show title={error.title} description={error.message} onClose={clearError} />} + <ConnectionLayout + TopContent={ + <Box> + <ConnectionStatus status={ConnectionStatusKind.disconnected} gatewayPerformance="Good" /> + <ConnectionTimer /> + </Box> + } + ConnectButton={<PowerButton onClick={onConnectClick} status={status} disabled={status === 'connecting'} />} + BottomContent={ + <Stack justifyContent="space-between" pt={1}> + <Typography + fontWeight={600} + textTransform="uppercase" + textAlign="center" + fontSize="12px" + sx={{ wordSpacing: 1.5, letterSpacing: 1.5 }} + color="warning.main" + > + You are not protected + </Typography> + <ExperimentalWarning /> + </Stack> + } + /> + </> +); diff --git a/nym-connect/desktop/src/pages/connection/index.tsx b/nym-connect/desktop/src/pages/connection/index.tsx new file mode 100644 index 0000000000..2ba081331f --- /dev/null +++ b/nym-connect/desktop/src/pages/connection/index.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { forage } from '@tauri-apps/tauri-forage'; +import { DateTime } from 'luxon'; +import { useClientContext } from 'src/context/main'; +import { useTauriEvents } from 'src/utils'; +import { Connected } from './Connected'; +import { Disconnected } from './Disconnected'; + +export const ConnectionPage = () => { + const context = useClientContext(); + const [busy, setBusy] = React.useState<boolean>(); + + useTauriEvents('help://clear-storage', (_event) => { + console.log('About to clear local storage...'); + // clear local storage + try { + forage.clear()(); + console.log('Local storage cleared'); + } catch (e) { + console.error('Failed to clear local storage', e); + } + }); + + const handleConnectClick = async () => { + const currentStatus = context.connectionStatus; + if (currentStatus === 'connected' || currentStatus === 'disconnected') { + setBusy(true); + // eslint-disable-next-line default-case + switch (currentStatus) { + case 'disconnected': + await context.setSerivceProvider(); + await context.startConnecting(); + context.setConnectedSince(DateTime.now()); + context.setShowInfoModal(true); + break; + case 'connected': + await context.startDisconnecting(); + context.setConnectedSince(undefined); + break; + } + setBusy(false); + } + }; + + const closeInfoModal = () => context.setShowInfoModal(false); + + if (context.connectionStatus === 'connected') + return ( + <Connected + error={context.error} + clearError={context.clearError} + status={context.connectionStatus} + showInfoModal={context.showInfoModal} + busy={busy} + onConnectClick={handleConnectClick} + ipAddress="127.0.0.1" + port={1080} + gatewayPerformance={context.gatewayPerformance} + connectedSince={context.connectedSince} + serviceProvider={context.selectedProvider} + closeInfoModal={closeInfoModal} + stats={[ + { + label: 'in:', + totalBytes: 1024, + rateBytesPerSecond: 1024 * 1024 * 1024 + 10, + }, + { + label: 'out:', + totalBytes: 1024 * 1024 * 1024 * 1024 * 20, + rateBytesPerSecond: 1024 * 1024 + 10, + }, + ]} + /> + ); + + return ( + <Disconnected + busy={busy} + error={context.error} + onConnectClick={handleConnectClick} + clearError={context.clearError} + status={context.connectionStatus} + serviceProvider={context.selectedProvider} + /> + ); +}; diff --git a/nym-connect/desktop/src/pages/menu/Apps.tsx b/nym-connect/desktop/src/pages/menu/Apps.tsx new file mode 100644 index 0000000000..ebe31deb44 --- /dev/null +++ b/nym-connect/desktop/src/pages/menu/Apps.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Divider, Typography } from '@mui/material'; +import { Box } from '@mui/system'; + +const appsSchema = { + messagingApps: ['Telegram', 'Keybase'], + wallets: ['Blockstream', 'Electrum'], +}; + +export const CompatibleApps = () => ( + <Box> + <Typography fontSize="small" color="grey.600" sx={{ mb: 2 }}> + Supported apps + </Typography> + <Typography color="nym.highlight" sx={{ mb: 2 }}> + Messaging apps + </Typography> + + <Divider sx={{ mb: 2 }} /> + + <Box sx={{ mb: 4 }}> + {appsSchema.messagingApps.map((app) => ( + <Typography variant="body2" color="grey.400" sx={{ mb: 2 }} key={app}> + {app} + </Typography> + ))} + </Box> + <Typography color="nym.highlight" sx={{ mb: 2 }}> + Wallets + </Typography> + + <Divider sx={{ mb: 2 }} /> + + <Box sx={{ mb: 4 }}> + {appsSchema.wallets.map((wallet) => ( + <Typography variant="body2" color="grey.400" sx={{ mb: 2 }} key={wallet}> + {wallet} + </Typography> + ))} + </Box> + </Box> +); diff --git a/nym-connect/desktop/src/pages/menu/Guide.tsx b/nym-connect/desktop/src/pages/menu/Guide.tsx new file mode 100644 index 0000000000..2323d68a82 --- /dev/null +++ b/nym-connect/desktop/src/pages/menu/Guide.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import { HelpPage } from 'src/components/HelpPage'; +import Image1 from '../../assets/help-step-one.png'; +import Image2 from '../../assets/help-step-two.png'; +import Image4 from '../../assets/help-step-four.png'; + +export const HelpGuide = () => { + const [step, setStep] = useState(1); + const TOTAL_STEPS = 3; + + if (step === 1) + return ( + <HelpPage + step={step} + totalSteps={TOTAL_STEPS} + description="Click button and connect + to a Nym mixnet." + img={Image1} + onNext={() => setStep(2)} + /> + ); + + if (step === 2) + return ( + <HelpPage + step={step} + totalSteps={TOTAL_STEPS} + description="Click on IP and Port to copy their values to the clipboard." + img={Image2} + onPrev={() => setStep(1)} + onNext={() => setStep(3)} + /> + ); + + if (step === 3) + return ( + <HelpPage + step={step} + totalSteps={TOTAL_STEPS} + description="Go to settings in your app, select run via SOCKS5 proxy and paste the IP and Port values given by NymConnect." + img={Image4} + onPrev={() => setStep(2)} + /> + ); + + return null; +}; diff --git a/nym-connect/desktop/src/pages/menu/index.tsx b/nym-connect/desktop/src/pages/menu/index.tsx new file mode 100644 index 0000000000..67b3dc0131 --- /dev/null +++ b/nym-connect/desktop/src/pages/menu/index.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Apps, HelpOutline, Settings } from '@mui/icons-material'; +import { Stack, Link, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; +import { Link as RouterLink } from 'react-router-dom'; +import { AppVersion } from 'src/components/AppVersion'; + +const menuSchema = [ + { title: 'Supported apps', icon: Apps, path: 'apps' }, + { title: 'How to connect guide', icon: HelpOutline, path: 'guide' }, + { title: 'Settings', icon: Settings, path: 'settings' }, +]; + +export const Menu = () => ( + <Stack justifyContent="space-between" height="100%"> + <List dense disablePadding> + {menuSchema.map((item) => ( + <Link component={RouterLink} to={item.path} underline="none" color="white" key={item.title}> + <ListItem disablePadding> + <ListItemButton> + <ListItemIcon sx={{ minWidth: 25 }}> + <item.icon sx={{ fontSize: '12px' }} /> + </ListItemIcon>{' '} + <ListItemText>{item.title}</ListItemText> + </ListItemButton> + </ListItem> + </Link> + ))} + </List> + <AppVersion /> + </Stack> +); diff --git a/nym-connect/desktop/src/pages/menu/settings/GatewaySettings.tsx b/nym-connect/desktop/src/pages/menu/settings/GatewaySettings.tsx new file mode 100644 index 0000000000..80efa7988a --- /dev/null +++ b/nym-connect/desktop/src/pages/menu/settings/GatewaySettings.tsx @@ -0,0 +1,84 @@ +import React, { ChangeEvent, useState } from 'react'; +import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; +import { Box, FormControl, FormControlLabel, FormHelperText, Link, Stack, Switch, Typography } from '@mui/material'; +import { useClientContext } from 'src/context/main'; +import { ConnectionStatusKind } from 'src/types'; +import { AppVersion } from 'src/components/AppVersion'; + +export const GatewaySettings = () => { + const { userDefinedGateway, setUserDefinedGateway, connectionStatus } = useClientContext(); + const [gatewayKey, setGatewayKey] = useState<string | undefined>(userDefinedGateway?.gateway); + + const handleIsValidGatewayKey = (isValid: boolean) => { + let gateway: string | undefined; + + if (isValid) { + gateway = gatewayKey; + } + + setUserDefinedGateway((current) => ({ ...current, gateway })); + }; + + const handleChange = (e: ChangeEvent<HTMLInputElement>) => { + setUserDefinedGateway((current) => ({ ...current, isActive: e.target.checked })); + }; + + return ( + <Box height="100%"> + <Stack justifyContent="space-between" height="100%"> + <Box> + <Typography fontWeight="bold" variant="body2" mb={1}> + Select your Gateway + </Typography> + <Typography color="grey.300" variant="body2" mb={2}> + Use a gateway of your choice + </Typography> + <FormControl fullWidth> + <FormControlLabel + control={ + <Switch + checked={userDefinedGateway?.isActive} + onChange={handleChange} + disabled={connectionStatus === ConnectionStatusKind.connected} + size="small" + sx={{ ml: 1 }} + /> + } + label={userDefinedGateway?.isActive ? 'On' : 'Off'} + /> + {connectionStatus === ConnectionStatusKind.connected && ( + <FormHelperText sx={{ m: 0, my: 1 }}>This setting is disabled during an active connection</FormHelperText> + )} + {userDefinedGateway?.isActive && ( + <IdentityKeyFormField + size="small" + placeholder="Gateway identity key" + onChanged={setGatewayKey} + initialValue={gatewayKey} + onValidate={handleIsValidGatewayKey} + sx={{ mt: 1 }} + disabled={connectionStatus === 'connected' || !userDefinedGateway?.isActive} + autoFocus + /> + )} + </FormControl> + </Box> + <Box> + <Typography variant="body2" mb={3}> + To find a gateway go to the{' '} + <Link + underline="none" + target="_blank" + href="https://explorer.nymtech.net/network-components/gateways" + sx={{ cursor: 'pointer' }} + color="nym.cta" + > + Network Explorer + </Link> + </Typography> + <AppVersion /> + </Box> + </Stack> + </Box> + ); +}; diff --git a/nym-connect/desktop/src/pages/menu/settings/ServiceProviderSettings.tsx b/nym-connect/desktop/src/pages/menu/settings/ServiceProviderSettings.tsx new file mode 100644 index 0000000000..0d0a0cdd20 --- /dev/null +++ b/nym-connect/desktop/src/pages/menu/settings/ServiceProviderSettings.tsx @@ -0,0 +1,101 @@ +import React, { ChangeEvent } from 'react'; +import { + Autocomplete, + Box, + FormControl, + FormControlLabel, + FormHelperText, + Stack, + Switch, + TextField, + Typography, +} from '@mui/material'; +import { AppVersion } from 'src/components/AppVersion'; +import { ConnectionStatusKind } from 'src/types'; +import { useClientContext } from 'src/context/main'; + +export const ServiceProviderSettings = () => { + const { connectionStatus, serviceProviders, userDefinedSPAddress, setUserDefinedSPAddress } = useClientContext(); + + const toggleOnOff = (e: ChangeEvent<HTMLInputElement>) => { + setUserDefinedSPAddress((current) => ({ ...current, isActive: e.target.checked })); + }; + + const handleSelectFromList = (value: string | null) => { + setUserDefinedSPAddress((current) => ({ ...current, address: value ?? undefined })); + }; + + const getSPDescription = (spAddress: string) => { + const match = serviceProviders?.find((sp) => sp.address === spAddress); + + if (match) return match.description; + + return 'N/A'; + }; + + const validateInput = (value: string) => { + setUserDefinedSPAddress((current) => ({ ...current, address: !value.length ? undefined : value })); + }; + + return ( + <Box height="100%"> + <Stack justifyContent="space-between" height="100%"> + <Box> + <Typography fontWeight="bold" variant="body2" mb={1}> + Select your Service Provider + </Typography> + <Typography color="grey.300" variant="body2" mb={2}> + Pick a service provider from the list or enter your own + </Typography> + <FormControl fullWidth> + <FormControlLabel + control={ + <Switch + checked={userDefinedSPAddress.isActive} + onChange={toggleOnOff} + disabled={connectionStatus === ConnectionStatusKind.connected} + size="small" + sx={{ ml: 1 }} + /> + } + label={userDefinedSPAddress.isActive ? 'On' : 'Off'} + /> + {connectionStatus === ConnectionStatusKind.connected && ( + <FormHelperText sx={{ m: 0, my: 1 }}>This setting is disabled during an active connection</FormHelperText> + )} + {userDefinedSPAddress.isActive && serviceProviders && ( + <Autocomplete + clearOnEscape + disabled={connectionStatus === 'connected'} + sx={{ mt: 1 }} + options={serviceProviders.map((sp) => sp.address)} + freeSolo + value={userDefinedSPAddress.address || ''} + onChange={(e, value) => handleSelectFromList(value)} + size="small" + renderInput={(params) => ( + <TextField + autoFocus + {...params} + placeholder="Service provider" + onChange={(e) => validateInput(e.target.value)} + /> + )} + ListboxProps={{ style: { background: 'unset', fontSize: '14px' } }} + /> + )} + </FormControl> + {userDefinedSPAddress.address && userDefinedSPAddress.isActive && ( + <Box sx={{ mt: 2 }}> + <Typography variant="body2">Name of Service Provider</Typography> + <Typography variant="body2" sx={{ mt: 0.5 }} color="grey.400"> + {getSPDescription(userDefinedSPAddress.address)} + </Typography> + </Box> + )} + </Box> + <AppVersion /> + </Stack> + </Box> + ); +}; diff --git a/nym-connect/desktop/src/pages/menu/settings/index.tsx b/nym-connect/desktop/src/pages/menu/settings/index.tsx new file mode 100644 index 0000000000..bc58b69f1b --- /dev/null +++ b/nym-connect/desktop/src/pages/menu/settings/index.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import { Link, List, ListItem, ListItemButton, ListItemText, Stack } from '@mui/material'; +import { AppVersion } from 'src/components/AppVersion'; +import { toggleLogViewer } from 'src/utils'; + +const menuSchema = [ + { title: 'Select your gateway', path: 'gateway' }, + { title: 'Select a service provider', path: 'service-provider' }, +]; + +export const SettingsMenu = () => ( + <Stack justifyContent="space-between" height="100%"> + <List dense disablePadding> + {menuSchema.map((item) => ( + <Link component={RouterLink} to={item.path} underline="none" color="white" key={item.title}> + <ListItem disablePadding> + <ListItemButton> + <ListItemText>{item.title}</ListItemText> + </ListItemButton> + </ListItem> + </Link> + ))} + <ListItem disablePadding> + <ListItemButton onClick={toggleLogViewer}> + <ListItemText>Logs</ListItemText> + </ListItemButton> + </ListItem> + </List> + <AppVersion /> + </Stack> +); diff --git a/nym-connect/desktop/src/routes/index.tsx b/nym-connect/desktop/src/routes/index.tsx new file mode 100644 index 0000000000..dd3eec678e --- /dev/null +++ b/nym-connect/desktop/src/routes/index.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Routes, Route } from 'react-router-dom'; +import { ConnectionPage } from 'src/pages/connection'; +import { Menu } from 'src/pages/menu'; +import { CompatibleApps } from 'src/pages/menu/Apps'; +import { HelpGuide } from 'src/pages/menu/Guide'; +import { SettingsMenu } from 'src/pages/menu/settings'; +import { GatewaySettings } from 'src/pages/menu/settings/GatewaySettings'; +import { ServiceProviderSettings } from 'src/pages/menu/settings/ServiceProviderSettings'; + +export const AppRoutes = () => ( + <Routes> + <Route index path="/" element={<ConnectionPage />} /> + <Route path="menu"> + <Route index element={<Menu />} /> + <Route path="apps" element={<CompatibleApps />} /> + <Route path="guide" element={<HelpGuide />} /> + <Route path="settings"> + <Route index element={<SettingsMenu />} /> + <Route path="gateway" element={<GatewaySettings />} /> + <Route path="service-provider" element={<ServiceProviderSettings />} /> + </Route> + </Route> + </Routes> +); diff --git a/nym-connect/src/stories/AppFlow.stories.tsx b/nym-connect/desktop/src/stories/AppFlow.stories.tsx similarity index 61% rename from nym-connect/src/stories/AppFlow.stories.tsx rename to nym-connect/desktop/src/stories/AppFlow.stories.tsx index 7055288f63..c00cb2aa9e 100644 --- a/nym-connect/src/stories/AppFlow.stories.tsx +++ b/nym-connect/desktop/src/stories/AppFlow.stories.tsx @@ -2,13 +2,12 @@ import React from 'react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { Box } from '@mui/material'; import { DateTime } from 'luxon'; +import { Disconnected } from 'src/pages/connection/Disconnected'; +import { Connected } from 'src/pages/connection/Connected'; +import { ConnectionStatusKind } from 'src/types'; import { AppWindowFrame } from '../components/AppWindowFrame'; import { useClientContext } from '../context/main'; -import { ConnectionStatusKind } from '../types'; -import { DefaultLayout } from '../layouts/DefaultLayout'; -import { ConnectedLayout } from '../layouts/ConnectedLayout'; import { Services } from '../types/directory'; -import { TestAndEarnButtonArea } from '../components/Growth/TestAndEarnButtonArea'; export default { title: 'App/Flow', @@ -37,15 +36,15 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => { ]; const handleConnectClick = React.useCallback(() => { const oldStatus = context.connectionStatus; - if (oldStatus === ConnectionStatusKind.connected || oldStatus === ConnectionStatusKind.disconnected) { + if (oldStatus === 'connected' || oldStatus === 'disconnected') { setBusy(true); // eslint-disable-next-line default-case switch (oldStatus) { - case ConnectionStatusKind.disconnected: + case 'disconnected': context.setConnectionStatus(ConnectionStatusKind.connecting); break; - case ConnectionStatusKind.connected: + case 'connected': context.setConnectionStatus(ConnectionStatusKind.disconnecting); break; } @@ -53,11 +52,11 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => { setTimeout(() => { // eslint-disable-next-line default-case switch (oldStatus) { - case ConnectionStatusKind.disconnected: + case 'disconnected': context.setConnectedSince(DateTime.now()); context.setConnectionStatus(ConnectionStatusKind.connected); break; - case ConnectionStatusKind.connected: + case 'connected': context.setConnectionStatus(ConnectionStatusKind.disconnected); break; } @@ -73,7 +72,7 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => { return ( <Box width={width} height={height}> <AppWindowFrame> - <DefaultLayout + <Disconnected status={context.connectionStatus} busy={busy} onConnectClick={handleConnectClick} @@ -86,32 +85,32 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => { } return ( - <Box width={width} height={height}> - <AppWindowFrame> - <ConnectedLayout - showInfoModal={false} - handleCloseInfoModal={() => undefined} - status={context.connectionStatus} - busy={busy} - onConnectClick={handleConnectClick} - ipAddress="127.0.0.1" - port={1080} - connectedSince={context.connectedSince} - serviceProvider={services[0].items[0]} - stats={[ - { - label: 'in:', - totalBytes: 1024, - rateBytesPerSecond: 1024 * 1024 * 1024 + 10, - }, - { - label: 'out:', - totalBytes: 1024 * 1024 * 1024 * 1024 * 20, - rateBytesPerSecond: 1024 * 1024 + 10, - }, - ]} - /> - </AppWindowFrame> - </Box> + <AppWindowFrame> + <Connected + clearError={() => {}} + gatewayPerformance="Good" + showInfoModal={false} + closeInfoModal={() => undefined} + status={context.connectionStatus} + busy={busy} + onConnectClick={handleConnectClick} + ipAddress="127.0.0.1" + port={1080} + connectedSince={context.connectedSince} + serviceProvider={services[0].items[0]} + stats={[ + { + label: 'in:', + totalBytes: 1024, + rateBytesPerSecond: 1024 * 1024 * 1024 + 10, + }, + { + label: 'out:', + totalBytes: 1024 * 1024 * 1024 * 1024 * 20, + rateBytesPerSecond: 1024 * 1024 + 10, + }, + ]} + /> + </AppWindowFrame> ); }; diff --git a/nym-connect/src/stories/AppWindowFrame.stories.tsx b/nym-connect/desktop/src/stories/AppWindowFrame.stories.tsx similarity index 100% rename from nym-connect/src/stories/AppWindowFrame.stories.tsx rename to nym-connect/desktop/src/stories/AppWindowFrame.stories.tsx diff --git a/nym-connect/src/stories/ConnectedLayout.stories.tsx b/nym-connect/desktop/src/stories/ConnectedLayout.stories.tsx similarity index 59% rename from nym-connect/src/stories/ConnectedLayout.stories.tsx rename to nym-connect/desktop/src/stories/ConnectedLayout.stories.tsx index 53538457bf..426252dc63 100644 --- a/nym-connect/src/stories/ConnectedLayout.stories.tsx +++ b/nym-connect/desktop/src/stories/ConnectedLayout.stories.tsx @@ -2,24 +2,27 @@ import React from 'react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { Box } from '@mui/material'; import { DateTime } from 'luxon'; -import { ConnectedLayout } from '../layouts/ConnectedLayout'; -import { ConnectionStatusKind } from '../types'; +import { Connected } from 'src/pages/connection/Connected'; +import { ConnectionStatusKind } from 'src/types'; + +const onClick = () => undefined; export default { title: 'Layouts/ConnectedLayout', - component: ConnectedLayout, -} as ComponentMeta<typeof ConnectedLayout>; + component: Connected, +} as ComponentMeta<typeof Connected>; -export const Default: ComponentStory<typeof ConnectedLayout> = () => ( +export const Default: ComponentStory<typeof Connected> = () => ( <Box p={2} width={242} sx={{ bgcolor: 'nym.background.dark' }}> - <ConnectedLayout + <Connected + clearError={() => {}} + gatewayPerformance="Good" showInfoModal={false} - handleCloseInfoModal={() => { - return undefined; - }} + closeInfoModal={() => undefined} status={ConnectionStatusKind.connected} connectedSince={DateTime.now()} ipAddress="127.0.0.1" + serviceProvider={{ id: 'service 1', description: 'good services', address: 'abc123', gateway: '8910xyz' }} port={1080} stats={[ { @@ -33,6 +36,7 @@ export const Default: ComponentStory<typeof ConnectedLayout> = () => ( rateBytesPerSecond: 1024 * 1024 + 10, }, ]} + onConnectClick={onClick} /> </Box> ); diff --git a/nym-connect/src/stories/ConnectionButton.stories.tsx b/nym-connect/desktop/src/stories/ConnectionButton.stories.tsx similarity index 94% rename from nym-connect/src/stories/ConnectionButton.stories.tsx rename to nym-connect/desktop/src/stories/ConnectionButton.stories.tsx index efce95ace4..6fcf7530ad 100644 --- a/nym-connect/src/stories/ConnectionButton.stories.tsx +++ b/nym-connect/desktop/src/stories/ConnectionButton.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { ConnectionStatusKind } from 'src/types'; import { ConnectionButton } from '../components/ConnectionButton'; -import { ConnectionStatusKind } from '../types'; export default { title: 'Components/ConnectionButton', diff --git a/nym-connect/src/stories/ConnectionStats.stories.tsx b/nym-connect/desktop/src/stories/ConnectionStats.stories.tsx similarity index 100% rename from nym-connect/src/stories/ConnectionStats.stories.tsx rename to nym-connect/desktop/src/stories/ConnectionStats.stories.tsx diff --git a/nym-connect/src/stories/ConnectionStatus.stories.tsx b/nym-connect/desktop/src/stories/ConnectionStatus.stories.tsx similarity index 75% rename from nym-connect/src/stories/ConnectionStatus.stories.tsx rename to nym-connect/desktop/src/stories/ConnectionStatus.stories.tsx index 10eb4d61ac..d98e262374 100644 --- a/nym-connect/src/stories/ConnectionStatus.stories.tsx +++ b/nym-connect/desktop/src/stories/ConnectionStatus.stories.tsx @@ -10,17 +10,17 @@ export default { } as ComponentMeta<typeof ConnectionStatus>; export const Disconnected: ComponentStory<typeof ConnectionStatus> = () => ( - <ConnectionStatus status={ConnectionStatusKind.disconnected} /> + <ConnectionStatus status={ConnectionStatusKind.disconnected} gatewayPerformance="Good" /> ); export const Connecting: ComponentStory<typeof ConnectionStatus> = () => ( - <ConnectionStatus status={ConnectionStatusKind.connecting} /> + <ConnectionStatus status={ConnectionStatusKind.connecting} gatewayPerformance="Good" /> ); export const Connected: ComponentStory<typeof ConnectionStatus> = () => ( - <ConnectionStatus status={ConnectionStatusKind.connected} connectedSince={DateTime.now()} /> + <ConnectionStatus status={ConnectionStatusKind.connected} connectedSince={DateTime.now()} gatewayPerformance="Good" /> ); export const Disconnecting: ComponentStory<typeof ConnectionStatus> = () => ( - <ConnectionStatus status={ConnectionStatusKind.disconnecting} /> + <ConnectionStatus status={ConnectionStatusKind.disconnecting} gatewayPerformance="Good" /> ); diff --git a/nym-connect/src/stories/DefaultLayout.stories.tsx b/nym-connect/desktop/src/stories/DefaultLayout.stories.tsx similarity index 57% rename from nym-connect/src/stories/DefaultLayout.stories.tsx rename to nym-connect/desktop/src/stories/DefaultLayout.stories.tsx index ba311a410b..4d1b869a10 100644 --- a/nym-connect/src/stories/DefaultLayout.stories.tsx +++ b/nym-connect/desktop/src/stories/DefaultLayout.stories.tsx @@ -1,23 +1,30 @@ import React from 'react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { Box } from '@mui/material'; -import { DefaultLayout } from '../layouts/DefaultLayout'; +import { Disconnected } from 'src/pages/connection/Disconnected'; import { ConnectionStatusKind } from '../types'; export default { title: 'Layouts/DefaultLayout', - component: DefaultLayout, -} as ComponentMeta<typeof DefaultLayout>; + component: Disconnected, +} as ComponentMeta<typeof Disconnected>; -export const Default: ComponentStory<typeof DefaultLayout> = () => ( +const onClick = () => undefined; + +export const Default: ComponentStory<typeof Disconnected> = () => ( <Box p={1} width={230} sx={{ bgcolor: 'nym.background.dark' }}> - <DefaultLayout status={ConnectionStatusKind.disconnected} clearError={() => {}} error={undefined} /> + <Disconnected + status={ConnectionStatusKind.disconnected} + clearError={() => {}} + error={undefined} + onConnectClick={onClick} + /> </Box> ); -export const WithServices: ComponentStory<typeof DefaultLayout> = () => ( +export const WithServices: ComponentStory<typeof Disconnected> = () => ( <Box p={1} width={230} sx={{ bgcolor: 'nym.background.dark' }}> - <DefaultLayout + <Disconnected status={ConnectionStatusKind.disconnected} services={[ { @@ -28,6 +35,7 @@ export const WithServices: ComponentStory<typeof DefaultLayout> = () => ( ]} clearError={() => {}} error={undefined} + onConnectClick={onClick} /> </Box> ); diff --git a/nym-connect/src/stories/Introduction.stories.mdx b/nym-connect/desktop/src/stories/Introduction.stories.mdx similarity index 100% rename from nym-connect/src/stories/Introduction.stories.mdx rename to nym-connect/desktop/src/stories/Introduction.stories.mdx diff --git a/nym-connect/src/stories/IpAddressAndPort.stories.tsx b/nym-connect/desktop/src/stories/IpAddressAndPort.stories.tsx similarity index 100% rename from nym-connect/src/stories/IpAddressAndPort.stories.tsx rename to nym-connect/desktop/src/stories/IpAddressAndPort.stories.tsx diff --git a/nym-connect/desktop/src/stories/PowerButton.stories.tsx b/nym-connect/desktop/src/stories/PowerButton.stories.tsx new file mode 100644 index 0000000000..6cb2896849 --- /dev/null +++ b/nym-connect/desktop/src/stories/PowerButton.stories.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { PowerButton } from 'src/components/PowerButton/PowerButton'; +import { ConnectionStatusKind } from 'src/types'; + +export default { + title: 'Components/PowerButton', + component: PowerButton, +} as ComponentMeta<typeof PowerButton>; + +export const Disconnected: ComponentStory<typeof PowerButton> = () => ( + <PowerButton status={ConnectionStatusKind.disconnected} /> +); + +export const Connecting: ComponentStory<typeof PowerButton> = () => ( + <PowerButton status={ConnectionStatusKind.connecting} disabled /> +); + +export const Connected: ComponentStory<typeof PowerButton> = () => ( + <PowerButton status={ConnectionStatusKind.connected} /> +); + +export const Disconnecting: ComponentStory<typeof PowerButton> = () => ( + <PowerButton status={ConnectionStatusKind.disconnecting} disabled /> +); + +export const Disabled: ComponentStory<typeof PowerButton> = () => ( + <PowerButton status={ConnectionStatusKind.disconnected} disabled /> +); diff --git a/nym-connect/src/theme/index.tsx b/nym-connect/desktop/src/theme/index.tsx similarity index 68% rename from nym-connect/src/theme/index.tsx rename to nym-connect/desktop/src/theme/index.tsx index 08f5c62924..d307fe46ee 100644 --- a/nym-connect/src/theme/index.tsx +++ b/nym-connect/desktop/src/theme/index.tsx @@ -2,12 +2,13 @@ import React from 'react'; import { createTheme, ThemeProvider } from '@mui/material/styles'; import { CssBaseline } from '@mui/material'; import { getDesignTokens } from './theme'; -import '../../../assets/fonts/non-variable/fonts.css'; +// eslint-disable-next-line import/no-relative-packages +import '../../../../assets/fonts/non-variable/fonts.css'; /** * Provides the theme for Nym Connect by reacting to the light/dark mode choice stored in the app context. */ -export const NymMixnetTheme: React.FC<{ mode: 'light' | 'dark' }> = ({ children, mode }) => { +export const NymMixnetTheme: FCWithChildren<{ mode: 'light' | 'dark' }> = ({ children, mode }) => { const theme = React.useMemo(() => createTheme(getDesignTokens(mode)), [mode]); return ( <ThemeProvider theme={theme}> @@ -17,7 +18,7 @@ export const NymMixnetTheme: React.FC<{ mode: 'light' | 'dark' }> = ({ children, ); }; -export const NymShipyardTheme: React.FC<{ mode?: 'light' | 'dark' }> = ({ children, mode = 'dark' }) => { +export const NymShipyardTheme: FCWithChildren<{ mode?: 'light' | 'dark' }> = ({ children, mode = 'dark' }) => { const theme = React.useMemo(() => createTheme(getDesignTokens(mode, true)), [mode]); return ( <ThemeProvider theme={theme}> diff --git a/nym-connect/src/theme/mui-theme.d.ts b/nym-connect/desktop/src/theme/mui-theme.d.ts similarity index 99% rename from nym-connect/src/theme/mui-theme.d.ts rename to nym-connect/desktop/src/theme/mui-theme.d.ts index 6c4d88fe39..76db4b1a99 100644 --- a/nym-connect/src/theme/mui-theme.d.ts +++ b/nym-connect/desktop/src/theme/mui-theme.d.ts @@ -29,6 +29,7 @@ declare module '@mui/material/styles' { */ interface NymPalette { highlight: string; + cta: string; success: string; warning: string; info: string; diff --git a/nym-connect/src/theme/theme.tsx b/nym-connect/desktop/src/theme/theme.tsx similarity index 98% rename from nym-connect/src/theme/theme.tsx rename to nym-connect/desktop/src/theme/theme.tsx index a580ae0b4e..977b0c9dfd 100644 --- a/nym-connect/src/theme/theme.tsx +++ b/nym-connect/desktop/src/theme/theme.tsx @@ -20,7 +20,8 @@ import { */ const nymPalette: NymPalette = { /** emphasises important elements */ - highlight: '#FB6E4E', + highlight: '#21D072', + cta: '#FB6E4E', success: '#21D073', info: '#60D7EF', warning: '#FFE600', @@ -73,6 +74,7 @@ const lightMode: NymPaletteVariant = { * IMPORTANT: do not export this constant, always use the MUI `useTheme` hook to get the correct * colours for dark/light mode. */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars const nymMixnetPalette = (variant: NymPaletteVariant): NymMixnetPalette => ({ nymMixnet: {}, }); diff --git a/nym-connect/src/types/connection.ts b/nym-connect/desktop/src/types/connection.ts similarity index 71% rename from nym-connect/src/types/connection.ts rename to nym-connect/desktop/src/types/connection.ts index f5665b58ea..8fb5234477 100644 --- a/nym-connect/src/types/connection.ts +++ b/nym-connect/desktop/src/types/connection.ts @@ -4,3 +4,5 @@ export enum ConnectionStatusKind { connected = 'connected', connecting = 'connecting', } + +export type GatewayPerformance = 'Good' | 'Poor' | 'VeryPoor'; diff --git a/nym-connect/src/types/directory.ts b/nym-connect/desktop/src/types/directory.ts similarity index 100% rename from nym-connect/src/types/directory.ts rename to nym-connect/desktop/src/types/directory.ts diff --git a/nym-connect/src/types/error.ts b/nym-connect/desktop/src/types/error.ts similarity index 100% rename from nym-connect/src/types/error.ts rename to nym-connect/desktop/src/types/error.ts diff --git a/nym-connect/src/types/event.ts b/nym-connect/desktop/src/types/event.ts similarity index 100% rename from nym-connect/src/types/event.ts rename to nym-connect/desktop/src/types/event.ts diff --git a/nym-connect/src/types/index.ts b/nym-connect/desktop/src/types/index.ts similarity index 100% rename from nym-connect/src/types/index.ts rename to nym-connect/desktop/src/types/index.ts diff --git a/nym-connect/src/types/rust/index.ts b/nym-connect/desktop/src/types/rust/index.ts similarity index 100% rename from nym-connect/src/types/rust/index.ts rename to nym-connect/desktop/src/types/rust/index.ts diff --git a/nym-connect/src/types/rust/stateparams.ts b/nym-connect/desktop/src/types/rust/stateparams.ts similarity index 100% rename from nym-connect/src/types/rust/stateparams.ts rename to nym-connect/desktop/src/types/rust/stateparams.ts diff --git a/nym-connect/desktop/src/types/service-provider.ts b/nym-connect/desktop/src/types/service-provider.ts new file mode 100644 index 0000000000..ed370e2755 --- /dev/null +++ b/nym-connect/desktop/src/types/service-provider.ts @@ -0,0 +1,9 @@ +export interface UserDefinedGateway { + isActive: boolean; + gateway?: string; +} + +export interface UserDefinedSPAddress { + isActive: boolean; + address?: string; +} diff --git a/nym-connect/desktop/src/types/storage.ts b/nym-connect/desktop/src/types/storage.ts new file mode 100644 index 0000000000..ca792b65d9 --- /dev/null +++ b/nym-connect/desktop/src/types/storage.ts @@ -0,0 +1,4 @@ +export interface StorageKeyValue<T> { + key: string; + value: T; +} diff --git a/nym-connect/desktop/src/typings/FC.d.ts b/nym-connect/desktop/src/typings/FC.d.ts new file mode 100644 index 0000000000..08ebdfa298 --- /dev/null +++ b/nym-connect/desktop/src/typings/FC.d.ts @@ -0,0 +1 @@ +declare type FCWithChildren<P = {}> = React.FC<React.PropsWithChildren<P>>; diff --git a/nym-connect/src/typings/jpeg.d.ts b/nym-connect/desktop/src/typings/jpeg.d.ts similarity index 100% rename from nym-connect/src/typings/jpeg.d.ts rename to nym-connect/desktop/src/typings/jpeg.d.ts diff --git a/nym-connect/src/typings/json.d.ts b/nym-connect/desktop/src/typings/json.d.ts similarity index 100% rename from nym-connect/src/typings/json.d.ts rename to nym-connect/desktop/src/typings/json.d.ts diff --git a/nym-connect/src/typings/png.d.ts b/nym-connect/desktop/src/typings/png.d.ts similarity index 100% rename from nym-connect/src/typings/png.d.ts rename to nym-connect/desktop/src/typings/png.d.ts diff --git a/nym-connect/src/typings/svg.d.ts b/nym-connect/desktop/src/typings/svg.d.ts similarity index 100% rename from nym-connect/src/typings/svg.d.ts rename to nym-connect/desktop/src/typings/svg.d.ts diff --git a/nym-connect/src/typings/webp.d.ts b/nym-connect/desktop/src/typings/webp.d.ts similarity index 100% rename from nym-connect/src/typings/webp.d.ts rename to nym-connect/desktop/src/typings/webp.d.ts diff --git a/nym-connect/src/typings/yaml.d.ts b/nym-connect/desktop/src/typings/yaml.d.ts similarity index 100% rename from nym-connect/src/typings/yaml.d.ts rename to nym-connect/desktop/src/typings/yaml.d.ts diff --git a/nym-connect/desktop/src/utils/index.ts b/nym-connect/desktop/src/utils/index.ts new file mode 100644 index 0000000000..04b415c026 --- /dev/null +++ b/nym-connect/desktop/src/utils/index.ts @@ -0,0 +1,48 @@ +import { useEffect, useRef } from 'react'; +import { EventName, listen, UnlistenFn, EventCallback } from '@tauri-apps/api/event'; +import { invoke } from '@tauri-apps/api'; +import { forage } from '@tauri-apps/tauri-forage'; +import { StorageKeyValue } from 'src/types/storage'; + +export const useTauriEvents = <T>(event: EventName, handler: EventCallback<T>) => { + const unlisten = useRef<UnlistenFn>(); + + // list for events to clear local storage + useEffect(() => { + listen(event, handler).then((fn) => { + unlisten.current = fn; + }); + + return () => { + if (unlisten.current) { + unlisten.current(); + } + }; + }, []); +}; + +export const toggleLogViewer = async () => { + await invoke('help_log_toggle_window'); +}; + +export async function setItemInStorage<T>({ key, value }: StorageKeyValue<T>) { + try { + await forage.setItem({ + key, + value, + } as any)(); + } catch (e) { + console.warn(e); + } + return undefined; +} + +export const getItemFromStorage = async ({ key }: Pick<StorageKeyValue<undefined>, 'key'>) => { + try { + const gatewayFromStorage = await forage.getItem({ key })(); + return gatewayFromStorage; + } catch (e) { + console.warn(e); + } + return undefined; +}; diff --git a/nym-connect/tsconfig.eslint.json b/nym-connect/desktop/tsconfig.eslint.json similarity index 100% rename from nym-connect/tsconfig.eslint.json rename to nym-connect/desktop/tsconfig.eslint.json diff --git a/nym-connect/tsconfig.json b/nym-connect/desktop/tsconfig.json similarity index 100% rename from nym-connect/tsconfig.json rename to nym-connect/desktop/tsconfig.json diff --git a/nym-connect/webpack.common.js b/nym-connect/desktop/webpack.common.js similarity index 100% rename from nym-connect/webpack.common.js rename to nym-connect/desktop/webpack.common.js diff --git a/nym-connect/webpack.dev.js b/nym-connect/desktop/webpack.dev.js similarity index 100% rename from nym-connect/webpack.dev.js rename to nym-connect/desktop/webpack.dev.js diff --git a/nym-connect/webpack.prod.js b/nym-connect/desktop/webpack.prod.js similarity index 100% rename from nym-connect/webpack.prod.js rename to nym-connect/desktop/webpack.prod.js diff --git a/nym-connect/mobile/.babelrc b/nym-connect/mobile/.babelrc new file mode 100644 index 0000000000..7a840ad24e --- /dev/null +++ b/nym-connect/mobile/.babelrc @@ -0,0 +1,15 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "esmodules": true + } + } + ], + "@babel/preset-react", + "@babel/preset-typescript" + ], + "plugins": ["@babel/plugin-transform-async-to-generator"] +} diff --git a/nym-connect/mobile/.env.sample b/nym-connect/mobile/.env.sample new file mode 100644 index 0000000000..cc3febee19 --- /dev/null +++ b/nym-connect/mobile/.env.sample @@ -0,0 +1 @@ +ADMIN_ADDRESS= \ No newline at end of file diff --git a/nym-connect/mobile/.eslintrc b/nym-connect/mobile/.eslintrc new file mode 100644 index 0000000000..3ea1281af1 --- /dev/null +++ b/nym-connect/mobile/.eslintrc @@ -0,0 +1,8 @@ +{ + "extends": [ + "@nymproject/eslint-config-react-typescript" + ], + "parserOptions": { + "project": "./tsconfig.eslint.json" + } +} diff --git a/nym-connect/mobile/.nvmrc b/nym-connect/mobile/.nvmrc new file mode 100644 index 0000000000..da2d3988d7 --- /dev/null +++ b/nym-connect/mobile/.nvmrc @@ -0,0 +1 @@ +14 \ No newline at end of file diff --git a/nym-connect/mobile/.prettierrc b/nym-connect/mobile/.prettierrc new file mode 100644 index 0000000000..1bc34d0c3f --- /dev/null +++ b/nym-connect/mobile/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "all", + "singleQuote": true, + "printWidth": 120, + "tabWidth": 2, + "semi": true +} diff --git a/nym-connect/mobile/.storybook/main.js b/nym-connect/mobile/.storybook/main.js new file mode 100644 index 0000000000..344c27778e --- /dev/null +++ b/nym-connect/mobile/.storybook/main.js @@ -0,0 +1,73 @@ +/* eslint-disable no-param-reassign */ +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); + +module.exports = { + stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'], + framework: '@storybook/react', + core: { + builder: 'webpack5', + }, + typescript: { reactDocgen: false }, + // webpackFinal: async (config, { configType }) => { + // // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' + // // You can change the configuration based on that. + // // 'PRODUCTION' is used when building the static version of storybook. + webpackFinal: async (config) => { + config.module.rules.forEach((rule) => { + // look for SVG import rule and replace + // NOTE: the rule before modification is /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/ + if (rule.test?.toString().includes('svg')) { + rule.test = /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/; + } + }); + + // handle asset loading with this + config.module.rules.unshift({ + test: /\.svg(\?.*)?$/i, + issuer: /\.[jt]sx?$/, + use: ['@svgr/webpack'], + }); + + config.module.rules.unshift({ + test: /\.ya?ml$/, + type: 'json', + use: [ + { + loader: 'yaml-loader', + options: { + asJSON: true, + }, + }, + ], + }); + + config.resolve.extensions = ['.tsx', '.ts', '.js']; + config.resolve.plugins = [new TsconfigPathsPlugin()]; + + config.plugins.push( + new ForkTsCheckerWebpackPlugin({ + typescript: { + mode: 'write-references', + diagnosticOptions: { + semantic: true, + syntactic: true, + }, + }, + }), + ); + + if (!config.resolve.alias) { + config.resolve.alias = {}; + } + + config.resolve.alias['@tauri-apps/api'] = `${__dirname}/mocks/tauri`; + + // Return the altered config + return config; + }, + features: { + emotionAlias: false, + }, +}; diff --git a/nym-connect/mobile/.storybook/mocks/tauri/app.js b/nym-connect/mobile/.storybook/mocks/tauri/app.js new file mode 100644 index 0000000000..83d80d7037 --- /dev/null +++ b/nym-connect/mobile/.storybook/mocks/tauri/app.js @@ -0,0 +1,8 @@ +/** + * This is a mock for Tauri's API package (@tauri-apps/api/app), to prevent stories from being excluded, because they either use + * or import dependencies that use Tauri. + */ + +module.exports = { + getVersion: () => undefined, +}; diff --git a/nym-connect/mobile/.storybook/mocks/tauri/clipboard.js b/nym-connect/mobile/.storybook/mocks/tauri/clipboard.js new file mode 100644 index 0000000000..a34847db86 --- /dev/null +++ b/nym-connect/mobile/.storybook/mocks/tauri/clipboard.js @@ -0,0 +1,8 @@ +/** + * This is a mock for Tauri's API package (@tauri-apps/api/clipboard), to prevent stories from being excluded, because they either use + * or import dependencies that use Tauri. + */ + +module.exports = { + writeText: async () => undefined, +} diff --git a/nym-connect/mobile/.storybook/mocks/tauri/event.js b/nym-connect/mobile/.storybook/mocks/tauri/event.js new file mode 100644 index 0000000000..a100d30337 --- /dev/null +++ b/nym-connect/mobile/.storybook/mocks/tauri/event.js @@ -0,0 +1,8 @@ +/** + * This is a mock for Tauri's API package (@tauri-apps/api/clipboard), to prevent stories from being excluded, because they either use + * or import dependencies that use Tauri. + */ + +module.exports = { + listen: async () => undefined, +} diff --git a/nym-connect/mobile/.storybook/mocks/tauri/index.js b/nym-connect/mobile/.storybook/mocks/tauri/index.js new file mode 100644 index 0000000000..c411c3bf18 --- /dev/null +++ b/nym-connect/mobile/.storybook/mocks/tauri/index.js @@ -0,0 +1,14 @@ +/** + * This is a mock for Tauri's API package (@tauri-apps/api), to prevent stories from being excluded, because they either use + * or import dependencies that use Tauri. + */ +module.exports = { + invoke: (operation, args) => { + console.error( + `Tauri cannot be used in Storybook. The operation requested was "${operation}". You can add mock responses to "nym_connect/.storybook/mocks/tauri.js" if you need. The default response is "void".`, + ); + return new Promise((resolve, reject) => { + reject(new Error(`Tauri operation ${operation} not available in storybook.`)); + }); + }, +}; diff --git a/nym-connect/mobile/.storybook/mocks/tauri/notification.js b/nym-connect/mobile/.storybook/mocks/tauri/notification.js new file mode 100644 index 0000000000..dcbe18e574 --- /dev/null +++ b/nym-connect/mobile/.storybook/mocks/tauri/notification.js @@ -0,0 +1,10 @@ +/** + * This is a mock for Tauri's API package (@tauri-apps/api/notification), to prevent stories from being excluded, because they either use + * or import dependencies that use Tauri. + */ + +module.exports = { + isPermissionGranted: () => undefined, + requestPermission: () => undefined, + sendNotification: () => undefined, +}; \ No newline at end of file diff --git a/nym-connect/mobile/.storybook/mocks/tauri/window.js b/nym-connect/mobile/.storybook/mocks/tauri/window.js new file mode 100644 index 0000000000..9f0afeeebd --- /dev/null +++ b/nym-connect/mobile/.storybook/mocks/tauri/window.js @@ -0,0 +1,10 @@ +/** + * This is a mock for Tauri's API package (@tauri-apps/api/window), to prevent stories from being excluded, because they either use + * or import dependencies that use Tauri. + */ + +module.exports = { + appWindow: { + maximize: () => undefined, + } +} diff --git a/nym-connect/mobile/.storybook/preview-fonts.js b/nym-connect/mobile/.storybook/preview-fonts.js new file mode 100644 index 0000000000..a7989fca9a --- /dev/null +++ b/nym-connect/mobile/.storybook/preview-fonts.js @@ -0,0 +1,3 @@ +import '../src/fonts/fonts.css'; + +export const Fonts = ({ children }) => <>{children}</>; \ No newline at end of file diff --git a/nym-connect/mobile/.storybook/preview.js b/nym-connect/mobile/.storybook/preview.js new file mode 100644 index 0000000000..9c14c3ec99 --- /dev/null +++ b/nym-connect/mobile/.storybook/preview.js @@ -0,0 +1,26 @@ +import { NymMixnetTheme } from '../src/theme'; +import { Fonts } from './preview-fonts'; +import { MockProvider } from '../src/context/mocks/main'; +const withThemeProvider = (Story, context) => { + return ( + <Fonts> + <MockProvider> + <NymMixnetTheme mode="dark"> + <Story {...context} /> + </NymMixnetTheme> + </MockProvider> + </Fonts> + ); +}; + +export const decorators = [withThemeProvider]; + +export const parameters = { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +}; diff --git a/nym-connect/CHANGELOG.md b/nym-connect/mobile/CHANGELOG.md similarity index 83% rename from nym-connect/CHANGELOG.md rename to nym-connect/mobile/CHANGELOG.md index 8e6d4eb168..cddba036e0 100644 --- a/nym-connect/CHANGELOG.md +++ b/nym-connect/mobile/CHANGELOG.md @@ -1,5 +1,25 @@ +# Changelog + ## UNRELEASED +## [nym-connect-v1.1.7](https://github.com/nymtech/nym/tree/nym-connect-v1.1.7) (2023-01-24) + +- Remove test and earn ([#2865]) + +[#2865]: https://github.com/nymtech/nym/issue/2865 + +## [nym-connect-v1.1.6](https://github.com/nymtech/nym/tree/nym-connect-v1.1.6) (2023-01-17) + +- part (1) show gateway status on the UI if the gateway is not live, is overloaded or is slow ([#2824]) + +[#2824]: https://github.com/nymtech/nym/pull/2824 + +## [nym-connect-v1.1.5](https://github.com/nymtech/nym/tree/nym-connect-v1.1.5) (2023-01-10) + +- get version number from tauri and display by @fmtabbara in https://github.com/nymtech/nym/pull/2684 +- Feature/nym connect experimental software text by @fmtabbara in https://github.com/nymtech/nym/pull/2692 +- NymConnect - Display service info in tooltip **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2704 + ## [nym-connect-v1.1.4](https://github.com/nymtech/nym/tree/nym-connect-v1.1.4) (2022-12-20) This release contains the new opt-in Test & Earn program, and it uses a stress-tested directory of network requesters to improve reliability. It also has some bugfixes, performance improvements, and better error handling. @@ -35,7 +55,6 @@ This release contains the new opt-in Test & Earn program, and it uses a stress-t [#1789]: https://github.com/nymtech/nym/pull/1789 [#1805]: https://github.com/nymtech/nym/pull/1805 - ## [nym-connect-v1.1.0](https://github.com/nymtech/nym/tree/nym-connect-v1.1.0) (2022-11-09) - nym-connect: rework of rewarding changes the directory data structures that describe the mixnet topology ([#1472]) @@ -44,7 +63,7 @@ This release contains the new opt-in Test & Earn program, and it uses a stress-t - native-client/socks5-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664]) - native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671]) - network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611]) -- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558]) +- validator-client: added `query_contract_smart` and `query_contract_raw` on `NyxdClient` ([#1558]) [#1472]: https://github.com/nymtech/nym/pull/1472 [#1558]: https://github.com/nymtech/nym/pull/1558 @@ -63,7 +82,6 @@ This release contains the new opt-in Test & Earn program, and it uses a stress-t [#1540]: https://github.com/nymtech/nym/pull/1540 - ## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22) ### Added diff --git a/nym-connect/mobile/README.md b/nym-connect/mobile/README.md new file mode 100644 index 0000000000..1bbc685447 --- /dev/null +++ b/nym-connect/mobile/README.md @@ -0,0 +1,69 @@ +<!-- +Copyright 2020 - Nym Technologies SA <contact@nymtech.net> +SPDX-License-Identifier: Apache-2.0 +--> + +# Nym Connect - Mobile + +[Nym Connect](https://github.com/nymtech/nym/tree/develop/nym-connect) application for Mobile. + +Nym Connects sets up a SOCKS5 proxy for local applications to use. + +**NOTE**: Currently we only focus on Android, +the remaining docs apply for Android development only. + +## Installation prerequisites - Linux / Mac + +- `Yarn` +- `NodeJS >= v16` +- `Rust & cargo` +- Android development environment (JDK, SDK/NDK, AVD etc...) + +For setting up an Android development environment see +https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/linux#android + +## Installation + +Inside the `mobile/nym-connect` directory, run the following commands: + +``` +yarn install +yarn prewebpack:dev +``` + +## Development + +Assuming there is a running android [emulator](https://developer.android.com/studio/run/emulator) +or a real [device](https://developer.android.com/studio/run/device) connected. +Inside the `mobile/nym-connect/src-tauri` directory, run the following command: + +``` +yarn dev:android +``` + +#### Debugging + +https://next--tauri.netlify.app/next/guides/debugging/application#mobile + +## Production + +To build the APK, run the build commands. + +``` +yarn webpack:prod +WRY_ANDROID_PACKAGE=net.nymtech.nym_connect WRY_ANDROID_LIBRARY=nym_connect cargo tauri android build --debug --apk +``` + +# Storybook + +Run storybook with: + +``` +yarn storybook +``` + +And build storybook static site with: + +``` +yarn storybook:build +``` diff --git a/nym-connect/mobile/dist/.gitkeep b/nym-connect/mobile/dist/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/nym-connect/mobile/package.json b/nym-connect/mobile/package.json new file mode 100644 index 0000000000..704f710c55 --- /dev/null +++ b/nym-connect/mobile/package.json @@ -0,0 +1,122 @@ +{ + "name": "@nym/nym-connect-mobile", + "version": "1.1.7", + "main": "index.js", + "license": "MIT", + "scripts": { + "prewebpack:dev": "yarn --cwd ../.. build", + "webpack:dev": "yarn webpack serve --config webpack.dev.js", + "webpack:dev:onlyThis": "yarn webpack serve --config webpack.dev.js", + "webpack:prod": "yarn webpack --progress --config webpack.prod.js", + "tauri": "tauri", + "tauri:dev:android": "WRY_ANDROID_PACKAGE=net.nymtech.nym_connect WRY_ANDROID_LIBRARY=nym_connect cargo tauri android dev", + "tauri:build:android": "WRY_ANDROID_PACKAGE=net.nymtech.nym_connect WRY_ANDROID_LIBRARY=nym_connect cargo tauri android build", + "dev:android": "run-p webpack:dev:onlyThis tauri:dev:android", + "prebuild": "yarn --cwd ../.. build", + "build:android": "run-s webpack:prod tauri:build:android", + "storybook": "start-storybook -p 6006", + "prestorybook:build": "yarn --cwd ../.. build", + "storybook:build": "build-storybook", + "tsc": "tsc --noEmit true", + "tsc:watch": "tsc --noEmit true --watch", + "lint": "eslint src", + "lint:fix": "eslint src --fix", + "test": "jest", + "test:watch": "jest --watch", + "clean:dist": "rm -rf dist", + "clean:node": "rm -rf node_modules", + "clean:rust": "cargo clean --manifest-path src-tauri/Cargo.toml", + "clean:android": "cd src-tauri/gen/android/nym_connect/ && gradlew clean", + "clean:": "run-p clean:node clean:rust clean:android" + }, + "dependencies": { + "@emotion/react": "^11.7.0", + "@emotion/styled": "^11.6.0", + "@hookform/resolvers": "^2.8.0", + "@mui/icons-material": "^5.2.0", + "@mui/material": "^5.2.2", + "@mui/styles": "^5.2.2", + "@mui/system": ">= 5", + "@mui/lab": "^5.0.0-alpha.72", + "@nymproject/react": "^1.0.0", + "@tauri-apps/api": "^2.0.0-alpha.0", + "@tauri-apps/tauri-forage": "^1.0.0-beta.2", + "clsx": "^1.1.1", + "luxon": "^2.3.0", + "pretty-bytes": "^6.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-error-boundary": "^3.1.3", + "react-hook-form": "^7.14.2", + "react-markdown": "^8.0.4", + "react-router-dom": "^6.7.0", + "semver": "^6.3.0", + "yup": "^0.32.9" + }, + "devDependencies": { + "@babel/core": "^7.15.0", + "@babel/plugin-transform-async-to-generator": "^7.14.5", + "@babel/preset-env": "^7.15.0", + "@babel/preset-react": "^7.14.5", + "@babel/preset-typescript": "^7.15.0", + "@mdx-js/loader": "^2.1.5", + "@nymproject/eslint-config-react-typescript": "^1.0.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", + "@storybook/react": "^6.5.15", + "@svgr/webpack": "^6.1.1", + "@tauri-apps/cli": "^2.0.0-alpha.2", + "@testing-library/jest-dom": "^5.14.1", + "@testing-library/react": "^12.0.0", + "@types/jest": "^27.0.1", + "@types/luxon": "^2.3.2", + "@types/node": "^16.7.13", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.10", + "@types/semver": "^7.3.8", + "@types/uuid": "^8.3.4", + "@typescript-eslint/eslint-plugin": "^5.13.0", + "@typescript-eslint/parser": "^5.13.0", + "babel-loader": "^8.3.0", + "babel-plugin-root-import": "^6.6.0", + "clean-webpack-plugin": "^4.0.0", + "css-loader": "^6.7.3", + "css-minimizer-webpack-plugin": "^3.0.2", + "dotenv-webpack": "^7.0.3", + "eslint": "^8.10.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^16.1.0", + "eslint-config-prettier": "^8.5.0", + "eslint-import-resolver-root-import": "^1.0.4", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^26.1.1", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react": "^7.29.2", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-storybook": "^0.5.12", + "favicons": "^7.0.2", + "favicons-webpack-plugin": "^5.0.2", + "file-loader": "^6.2.0", + "fork-ts-checker-webpack-plugin": "^7.2.1", + "html-webpack-plugin": "^5.3.2", + "jest": "^27.1.0", + "mini-css-extract-plugin": "^2.2.2", + "npm-run-all": "^4.1.5", + "prettier": "2.3.2", + "react-refresh": "^0.10.0", + "react-refresh-typescript": "^2.0.2", + "style-loader": "^3.3.1", + "thread-loader": "^3.0.4", + "ts-jest": "^27.0.5", + "ts-loader": "^9.4.2", + "tsconfig-paths-webpack-plugin": "^3.5.2", + "typescript": "^4.6.2", + "url-loader": "^4.1.1", + "webpack": "^5.75.0", + "webpack-cli": "^4.8.0", + "webpack-dev-server": "^4.5.0", + "webpack-favicons": "^1.3.8", + "webpack-merge": "^5.8.0", + "yaml-loader": "^0.8.0" + } +} diff --git a/nym-connect/mobile/public/favicon.ico b/nym-connect/mobile/public/favicon.ico new file mode 100644 index 0000000000..bc951a1c6f Binary files /dev/null and b/nym-connect/mobile/public/favicon.ico differ diff --git a/nym-connect/mobile/public/favicon.png b/nym-connect/mobile/public/favicon.png new file mode 100644 index 0000000000..fcf1e747d0 Binary files /dev/null and b/nym-connect/mobile/public/favicon.png differ diff --git a/nym-connect/mobile/public/index.html b/nym-connect/mobile/public/index.html new file mode 100644 index 0000000000..2ba643a663 --- /dev/null +++ b/nym-connect/mobile/public/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>Nym Connect + + +
+ + diff --git a/nym-connect/mobile/src-tauri/.gitignore b/nym-connect/mobile/src-tauri/.gitignore new file mode 100644 index 0000000000..296ceb2535 --- /dev/null +++ b/nym-connect/mobile/src-tauri/.gitignore @@ -0,0 +1,5 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +WixTools +/.cargo/ diff --git a/nym-connect/Cargo.lock b/nym-connect/mobile/src-tauri/Cargo.lock similarity index 81% rename from nym-connect/Cargo.lock rename to nym-connect/mobile/src-tauri/Cargo.lock index 427d578caf..696e24bcb6 100644 --- a/nym-connect/Cargo.lock +++ b/nym-connect/mobile/src-tauri/Cargo.lock @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ "cfg-if", "cipher 0.4.3", @@ -44,16 +44,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", "once_cell", "version_check", ] [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -82,20 +82,11 @@ dependencies = [ "libc", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "arrayref" @@ -117,9 +108,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", @@ -128,9 +119,9 @@ dependencies = [ [[package]] name = "atk" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" +checksum = "39991bc421ddf72f70159011b323ff49b0f783cc676a7287c59453da2e2531cf" dependencies = [ "atk-sys", "bitflags", @@ -140,14 +131,14 @@ dependencies = [ [[package]] name = "atk-sys" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +checksum = "11ad703eb64dc058024f0e57ccfa069e15a413b98dbd50a1a950e743b7f11148" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.2", + "system-deps", ] [[package]] @@ -170,9 +161,9 @@ dependencies = [ [[package]] name = "attohttpc" -version = "0.22.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7" +checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" dependencies = [ "flate2", "http", @@ -190,7 +181,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -218,15 +209,21 @@ checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "base64ct" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" [[package]] name = "bip32" @@ -248,9 +245,9 @@ dependencies = [ [[package]] name = "bip39" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e89470017230c38e52b82b3ee3f530db1856ba1d434e3a67a3456a8a8dec5f" +checksum = "f5b9d9748b5770d1539657653dc5ac3cd9353549e74238dc0d96c22919128b94" dependencies = [ "bitcoin_hashes", "rand 0.6.5", @@ -261,9 +258,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.9.7" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" [[package]] name = "bitflags" @@ -297,16 +294,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" dependencies = [ "arrayref", "arrayvec 0.7.2", "cc", "cfg-if", "constant_time_eq", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -381,9 +378,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.2" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -400,18 +397,19 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.17" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" dependencies = [ "memchr", + "serde", ] [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byte-tools" @@ -421,9 +419,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" +checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" [[package]] name = "byteorder" @@ -433,39 +431,40 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cairo-rs" -version = "0.15.12" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" +checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d" dependencies = [ "bitflags", "cairo-sys-rs", "glib", "libc", + "once_cell", "thiserror", ] [[package]] name = "cairo-sys-rs" -version = "0.15.1" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +checksum = "7c48f4af05fabdcfa9658178e1326efa061853f040ce7d72e33af6885196f421" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.2", + "system-deps", ] [[package]] name = "cargo_toml" -version = "0.13.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa0e3586af56b3bfa51fca452bd56e8dbbbd5d8d81cbf0b7e4e35b695b537eb8" +checksum = "497049e9477329f8f6a559972ee42e117487d01d1e8c2cc9f836ea6fa23a9e1a" dependencies = [ "serde", "toml", @@ -473,9 +472,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] @@ -498,20 +497,11 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.9.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" +checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" dependencies = [ - "smallvec 1.9.0", -] - -[[package]] -name = "cfg-expr" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" -dependencies = [ - "smallvec 1.9.0", + "smallvec 1.10.0", ] [[package]] @@ -532,15 +522,15 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", "num-integer", "num-traits", - "time 0.1.44", + "time 0.1.45", "wasm-bindgen", "winapi", ] @@ -566,35 +556,33 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.22" +version = "4.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" dependencies = [ - "atty", "bitflags", "clap_derive", "clap_lex", - "indexmap", + "is-terminal", "once_cell", "strsim", "termcolor", - "textwrap", ] [[package]] name = "clap_complete" -version = "3.2.5" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8" +checksum = "0012995dc3a54314f4710f5631d74767e73c534b8757221708303e48eef7a19b" dependencies = [ "clap", ] [[package]] name = "clap_complete_fig" -version = "3.2.4" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed37b4c0c1214673eba6ad8ea31666626bf72be98ffb323067d973c48b4964b9" +checksum = "63a06158a72dbb088f864887b4409fd22600ba379f3cee3040f842234cc5c2d0" dependencies = [ "clap", "clap_complete", @@ -602,11 +590,11 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.18" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", @@ -615,29 +603,18 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" dependencies = [ "os_str_bytes", ] -[[package]] -name = "client-connections" -version = "0.1.0" -dependencies = [ - "futures", - "log", -] - [[package]] name = "client-core" -version = "1.1.3" +version = "1.1.12" dependencies = [ "async-trait", - "client-connections", - "config", - "crypto", "dashmap", "dirs", "futures", @@ -646,20 +623,24 @@ dependencies = [ "gloo-timers", "humantime-serde", "log", - "nonexhaustive-delayqueue", - "nymsphinx", - "pemstore", + "nym-config", + "nym-crypto", + "nym-nonexhaustive-delayqueue", + "nym-pemstore", + "nym-sphinx", + "nym-task", + "nym-topology", "rand 0.7.3", "serde", "serde_json", "sqlx 0.6.2", "tap", - "task", "thiserror", - "time 0.3.17", + "time 0.3.19", "tokio", "tokio-stream", - "topology", + "tokio-tungstenite", + "tungstenite", "url", "validator-client", "wasm-bindgen", @@ -679,9 +660,9 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" dependencies = [ "bitflags", "block", @@ -709,36 +690,13 @@ dependencies = [ ] [[package]] -name = "coconut-bandwidth-contract-common" -version = "0.1.0" +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ - "cosmwasm-std", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-dkg-common" -version = "0.1.0" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "cw-utils", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-interface" -version = "0.1.0" -dependencies = [ - "bs58", - "getset", - "nymcoconut", - "serde", - "thiserror", + "termcolor", + "unicode-width", ] [[package]] @@ -779,28 +737,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "completions" -version = "0.1.0" -dependencies = [ - "clap", - "clap_complete", - "clap_complete_fig", -] - -[[package]] -name = "config" -version = "0.1.0" -dependencies = [ - "cfg-if", - "handlebars", - "log", - "network-defaults", - "serde", - "toml", - "url", -] - [[package]] name = "const-oid" version = "0.7.1" @@ -809,20 +745,9 @@ checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "constant_time_eq" -version = "0.1.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "contracts-common" -version = "0.1.0" -dependencies = [ - "bs58", - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] +checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" [[package]] name = "convert_case" @@ -890,7 +815,7 @@ dependencies = [ "cosmos-sdk-proto", "ecdsa", "eyre", - "getrandom 0.2.7", + "getrandom 0.2.8", "k256", "prost", "prost-types", @@ -918,9 +843,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.1.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0faf3a02389f78d6173b7e680751205015d5406f8abbaa9aa36fd216adc9f10d" +checksum = "d5abeeb891e6d0098402e4d3d042f90451db52651d2fe14b170e69a1dd3e4115" dependencies = [ "syn", ] @@ -931,7 +856,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" dependencies = [ - "base64", + "base64 0.13.1", "cosmwasm-crypto", "cosmwasm-derive", "forward_ref", @@ -962,11 +887,11 @@ dependencies = [ [[package]] name = "crc" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ - "crc-catalog 2.1.0", + "crc-catalog 2.2.0", ] [[package]] @@ -977,9 +902,9 @@ checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" [[package]] name = "crc-catalog" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" [[package]] name = "crc32fast" @@ -990,29 +915,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "credential-storage" -version = "0.1.0" -dependencies = [ - "async-trait", - "log", - "sqlx 0.5.13", - "thiserror", - "tokio", -] - -[[package]] -name = "credentials" -version = "0.1.0" -dependencies = [ - "bls12_381 0.5.0", - "coconut-interface", - "crypto", - "thiserror", - "validator-api-requests", - "validator-client", -] - [[package]] name = "crossbeam-channel" version = "0.5.6" @@ -1025,9 +927,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1035,12 +937,11 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -1049,27 +950,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto" -version = "0.1.0" -dependencies = [ - "aes 0.8.1", - "blake3", - "bs58", - "cipher 0.4.3", - "ctr 0.9.1", - "digest 0.10.5", - "ed25519-dalek", - "generic-array 0.14.6", - "hkdf 0.12.3", - "hmac 0.12.1", - "nymsphinx-types", - "pemstore", - "rand 0.7.3", - "subtle-encoding", - "x25519-dalek", -] - [[package]] name = "crypto-bigint" version = "0.3.2" @@ -1125,7 +1005,7 @@ dependencies = [ "phf 0.8.0", "proc-macro2", "quote", - "smallvec 1.9.0", + "smallvec 1.10.0", "syn", ] @@ -1150,9 +1030,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", "syn", @@ -1169,9 +1049,9 @@ dependencies = [ [[package]] name = "ctr" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d14f329cfbaf5d0e06b5e87fff7e265d2673c5ea7d2c27691a2c107db1442a0" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ "cipher 0.4.3", ] @@ -1270,6 +1150,50 @@ dependencies = [ "serde", ] +[[package]] +name = "cxx" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.13.4" @@ -1315,14 +1239,14 @@ dependencies = [ "hashbrown 0.12.3", "lock_api", "once_cell", - "parking_lot_core 0.9.3", + "parking_lot_core 0.9.7", ] [[package]] name = "dbus" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8bcdd56d2e5c4ed26a529c5a9029f5db8290d433497506f958eae3be148eb6" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" dependencies = [ "libc", "libdbus-sys", @@ -1371,9 +1295,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -1427,27 +1351,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" -[[package]] -name = "dkg" -version = "0.1.0" -dependencies = [ - "bitvec", - "bls12_381 0.6.0", - "bs58", - "ff 0.11.1", - "group 0.11.0", - "lazy_static", - "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 = "dotenv" version = "0.15.0" @@ -1483,9 +1386,9 @@ checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" [[package]] name = "dyn-clone" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" +checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" [[package]] name = "ecdsa" @@ -1501,9 +1404,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ "signature", ] @@ -1524,24 +1427,24 @@ dependencies = [ [[package]] name = "ed25519-zebra" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ "curve25519-dalek", + "hashbrown 0.12.3", "hex", "rand_core 0.6.4", "serde", "sha2 0.9.9", - "thiserror", "zeroize", ] [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" @@ -1569,27 +1472,27 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if", ] [[package]] name = "enum-iterator" -version = "0.8.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2953d1df47ac0eb70086ccabf0275aa8da8591a28bd358ee2b52bd9f9e3ff9e9" +checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401" dependencies = [ "enum-iterator-derive", ] [[package]] name = "enum-iterator-derive" -version = "0.8.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8958699f9359f0b04e691a13850d48b7de329138023876d07cbd024c2c820598" +checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce" dependencies = [ "proc-macro2", "quote", @@ -1609,20 +1512,33 @@ dependencies = [ "termcolor", ] +[[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-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" -[[package]] -name = "execute" -version = "0.1.0" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "eyre" version = "0.6.8" @@ -1635,9 +1551,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -1684,14 +1600,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" dependencies = [ "cfg-if", "libc", "redox_syscall", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1705,9 +1621,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", @@ -1732,7 +1648,7 @@ dependencies = [ "futures-core", "futures-sink", "pin-project", - "spin 0.9.4", + "spin 0.9.5", ] [[package]] @@ -1795,9 +1711,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -1810,9 +1726,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -1820,15 +1736,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -1837,9 +1753,9 @@ dependencies = [ [[package]] name = "futures-intrusive" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" dependencies = [ "futures-core", "lock_api", @@ -1848,15 +1764,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -1865,21 +1781,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -1907,24 +1823,28 @@ name = "gateway-client" version = "0.1.0" dependencies = [ "async-trait", - "credential-storage", - "credentials", - "crypto", "futures", "gateway-requests", - "getrandom 0.2.7", + "getrandom 0.2.8", "log", - "network-defaults", - "nymsphinx", - "pemstore", + "mobile-storage", + "nym-coconut-interface", + "nym-credential-storage", + "nym-credentials", + "nym-crypto", + "nym-network-defaults", + "nym-pemstore", + "nym-sphinx", + "nym-task", "rand 0.7.3", - "task", + "serde", "thiserror", "tokio", "tokio-stream", "tokio-tungstenite", "tungstenite", "url", + "validator-client", "wasm-bindgen", "wasm-bindgen-futures", "wasm-timer", @@ -1936,12 +1856,13 @@ name = "gateway-requests" version = "0.1.0" dependencies = [ "bs58", - "credentials", - "crypto", "futures", "log", - "nymsphinx", - "pemstore", + "nym-coconut-interface", + "nym-credentials", + "nym-crypto", + "nym-pemstore", + "nym-sphinx", "rand 0.7.3", "serde", "serde_json", @@ -1951,9 +1872,9 @@ dependencies = [ [[package]] name = "gdk" -version = "0.15.4" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" +checksum = "aa9cb33da481c6c040404a11f8212d193889e9b435db2c14fd86987f630d3ce1" dependencies = [ "bitflags", "cairo-rs", @@ -1967,9 +1888,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.15.11" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" +checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05" dependencies = [ "bitflags", "gdk-pixbuf-sys", @@ -1980,22 +1901,22 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" +checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016" dependencies = [ "gio-sys", "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.2", + "system-deps", ] [[package]] name = "gdk-sys" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" +checksum = "d76354f97a913e55b984759a997b693aa7dc71068c9e98bcce51aa167a0a5c5a" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -2005,33 +1926,33 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 6.0.2", + "system-deps", ] [[package]] name = "gdkx11-sys" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" +checksum = "9fa2bf8b5b8c414bc5d05e48b271896d0fd3ddb57464a3108438082da61de6af" dependencies = [ "gdk-sys", "glib-sys", "libc", - "system-deps 6.0.2", + "system-deps", "x11", ] [[package]] name = "generator" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc184cace1cea8335047a471cc1da80f18acf8a76f3bab2028d499e328948ec7" +checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" dependencies = [ "cc", "libc", "log", "rustversion", - "windows 0.32.0", + "windows 0.44.0", ] [[package]] @@ -2068,9 +1989,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "js-sys", @@ -2093,39 +2014,42 @@ dependencies = [ [[package]] name = "gio" -version = "0.15.12" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" +checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-io", + "futures-util", "gio-sys", "glib", "libc", "once_cell", + "pin-project-lite", + "smallvec 1.10.0", "thiserror", ] [[package]] name = "gio-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" +checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.2", + "system-deps", "winapi", ] [[package]] name = "git2" -version = "0.15.0" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1" +checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" dependencies = [ "bitflags", "libc", @@ -2136,32 +2060,34 @@ dependencies = [ [[package]] name = "glib" -version = "0.15.12" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" +checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-executor", "futures-task", + "futures-util", + "gio-sys", "glib-macros", "glib-sys", "gobject-sys", "libc", "once_cell", - "smallvec 1.9.0", + "smallvec 1.10.0", "thiserror", ] [[package]] name = "glib-macros" -version = "0.15.11" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" +checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf" dependencies = [ "anyhow", - "heck 0.4.0", + "heck 0.4.1", "proc-macro-crate", "proc-macro-error", "proc-macro2", @@ -2171,25 +2097,25 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" dependencies = [ "libc", - "system-deps 6.0.2", + "system-deps", ] [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ "aho-corasick", "bstr", @@ -2200,9 +2126,9 @@ dependencies = [ [[package]] name = "gloo-timers" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" dependencies = [ "futures-channel", "futures-core", @@ -2212,13 +2138,13 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.2", + "system-deps", ] [[package]] @@ -2247,9 +2173,9 @@ dependencies = [ [[package]] name = "gtk" -version = "0.15.5" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" +checksum = "e4d3507d43908c866c805f74c9dd593c0ce7ba5c38e576e41846639cdcd4bee6" dependencies = [ "atk", "bitflags", @@ -2270,9 +2196,9 @@ dependencies = [ [[package]] name = "gtk-sys" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" +checksum = "89b5f8946685d5fe44497007786600c2f368ff6b1e61a16251c89f72a97520a3" dependencies = [ "atk-sys", "cairo-sys-rs", @@ -2283,14 +2209,14 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps 6.0.2", + "system-deps", ] [[package]] name = "gtk3-macros" -version = "0.15.4" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9" +checksum = "8cfd6557b1018b773e43c8de9d0d13581d6b36190d0501916cbec4731db5ccff" dependencies = [ "anyhow", "proc-macro-crate", @@ -2302,9 +2228,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -2375,7 +2301,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64", + "base64 0.13.1", "bitflags", "bytes", "headers-core", @@ -2405,9 +2331,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" dependencies = [ "unicode-segmentation", ] @@ -2421,6 +2347,21 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -2468,7 +2409,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -2487,13 +2428,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.3", + "itoa 1.0.5", ] [[package]] @@ -2552,9 +2493,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ "bytes", "futures-channel", @@ -2565,7 +2506,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.3", + "itoa 1.0.5", "pin-project-lite", "socket2", "tokio", @@ -2626,18 +2567,28 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.48" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237a0714f28b1ee39ccec0770ccb544eb02c9ef2c82bb096230eefcffa6468b0" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" dependencies = [ "android_system_properties", "core-foundation-sys", + "iana-time-zone-haiku", "js-sys", - "once_cell", "wasm-bindgen", "winapi", ] +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "ico" version = "0.2.0" @@ -2685,9 +2636,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.3" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" dependencies = [ "bytemuck", "byteorder", @@ -2704,9 +2655,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg 1.1.0", "hashbrown 0.12.3", @@ -2753,10 +2704,32 @@ dependencies = [ ] [[package]] -name = "ipnet" -version = "2.5.0" +name = "io-lifetimes" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" +dependencies = [ + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "ipnet" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" + +[[package]] +name = "is-terminal" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] [[package]] name = "itertools" @@ -2775,15 +2748,15 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "javascriptcore-rs" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" +checksum = "110b9902c80c12bf113c432d0b71c7a94490b294a8234f326fd0abca2fac0b00" dependencies = [ "bitflags", "glib", @@ -2792,14 +2765,14 @@ dependencies = [ [[package]] name = "javascriptcore-rs-sys" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c" +checksum = "98a216519a52cd941a733a0ad3f1023cfdb1cd47f3955e8e863ed56f558f916c" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 5.0.0", + "system-deps", ] [[package]] @@ -2824,9 +2797,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] @@ -2842,9 +2815,9 @@ dependencies = [ [[package]] name = "json-patch" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f995a3c8f2bc3dd52a18a583e90f9ec109c047fa1603a853e46bcda14d2e279d" +checksum = "eb3fa5a61630976fc4c353c70297f2e93f1930e3ccee574d59d618ccbd5154ce" dependencies = [ "serde", "serde_json", @@ -2867,9 +2840,12 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] [[package]] name = "keystream" @@ -2897,9 +2873,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libappindicator" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2d3cb96d092b4824cb306c9e544c856a4cb6210c1081945187f7f1924b47e8" +checksum = "89e1edfdc9b0853358306c6dfb4b77c79c779174256fe93d80c0b5ebca451a2f" dependencies = [ "glib", "gtk", @@ -2910,9 +2886,9 @@ dependencies = [ [[package]] name = "libappindicator-sys" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b3b6681973cea8cc3bce7391e6d7d5502720b80a581c9a95c9cbaf592826aa" +checksum = "08fcb2bea89cee9613982501ec83eaa2d09256b24540ae463c52a28906163918" dependencies = [ "gtk-sys", "libloading", @@ -2921,24 +2897,24 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.132" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libdbus-sys" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b" +checksum = "9f8d7ae751e1cb825c840ae5e682f59b098cdfd213c350ac268b61449a5f58a0" dependencies = [ "pkg-config", ] [[package]] name = "libgit2-sys" -version = "0.14.0+1.5.0" +version = "0.13.5+1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a00859c70c8a4f7218e6d1cc32875c4b55f6799445b842b0d8ed5e4c3d959b" +checksum = "51e5ea06c26926f1002dd553fded6cfcdc9784c1f60feeb58368b4d9b07b6dba" dependencies = [ "cc", "libc", @@ -2948,9 +2924,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -2958,9 +2934,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "libsqlite3-sys" @@ -2994,12 +2970,27 @@ dependencies = [ "safemem", ] +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + [[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.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lioness" version = "0.1.2" @@ -3014,9 +3005,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg 1.1.0", "scopeguard", @@ -3032,14 +3023,6 @@ dependencies = [ "serde", ] -[[package]] -name = "logging" -version = "0.1.0" -dependencies = [ - "log", - "pretty_env_logger", -] - [[package]] name = "loom" version = "0.5.6" @@ -3071,7 +3054,7 @@ dependencies = [ "dirs-next", "objc-foundation", "objc_id", - "time 0.3.17", + "time 0.3.19", ] [[package]] @@ -3108,9 +3091,9 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "maybe-uninit" @@ -3145,68 +3128,40 @@ 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.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] -name = "mixnet-contract-common" +name = "mobile-storage" version = "0.1.0" dependencies = [ - "bs58", - "contracts-common", - "cosmwasm-std", - "humantime-serde", - "log", - "schemars", - "serde", - "serde_json", - "serde_repr", + "async-trait", "thiserror", - "time 0.3.17", -] - -[[package]] -name = "multisig-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "cw3", - "cw3-fixed-multisig", - "cw4", - "schemars", - "serde", ] [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -3248,19 +3203,6 @@ dependencies = [ "jni-sys", ] -[[package]] -name = "network-defaults" -version = "0.1.0" -dependencies = [ - "cfg-if", - "dotenv", - "hex-literal", - "once_cell", - "serde", - "thiserror", - "url", -] - [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -3275,37 +3217,45 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] -name = "nonexhaustive-delayqueue" -version = "0.1.0" +name = "nom8" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" dependencies = [ - "futures-core", - "slab", - "tokio", - "tokio-stream", - "tokio-util", - "wasm-timer", + "memchr", ] [[package]] name = "notify-rust" -version = "4.5.10" +version = "4.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368e89ea58df747ce88be669ae44e79783c1d30bfd540ad0fc520b3f41f0b3b0" +checksum = "2bfa211d18e360f08e36c364308f394b5eb23a6629150690e109a916dc6f610e" dependencies = [ "dbus", + "log", "mac-notification-sys", "tauri-winrt-notification", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -3350,28 +3300,28 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "num_enum" -version = "0.5.7" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.7" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -3379,16 +3329,88 @@ dependencies = [ "syn", ] +[[package]] +name = "nym-api-requests" +version = "0.1.0" +dependencies = [ + "bs58", + "cosmrs", + "cosmwasm-std", + "getset", + "nym-coconut-interface", + "nym-mixnet-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-bin-common" +version = "0.2.0" +dependencies = [ + "clap", + "clap_complete", + "clap_complete_fig", + "log", + "pretty_env_logger", + "semver 0.11.0", + "serde", + "vergen", +] + +[[package]] +name = "nym-coconut-bandwidth-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-dkg-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-interface" +version = "0.1.0" +dependencies = [ + "bs58", + "getset", + "nymcoconut", + "serde", + "thiserror", +] + +[[package]] +name = "nym-config" +version = "0.1.0" +dependencies = [ + "cfg-if", + "handlebars", + "log", + "nym-network-defaults", + "serde", + "toml", + "url", +] + [[package]] name = "nym-connect" -version = "1.1.3" +version = "1.1.9" dependencies = [ "anyhow", "bip39", "chrono", "client-core", - "config", - "crypto", "dirs", "eyre", "fern", @@ -3396,8 +3418,13 @@ dependencies = [ "futures", "itertools", "log", - "logging", + "nym-api-requests", + "nym-bin-common", + "nym-config", + "nym-contracts-common", + "nym-crypto", "nym-socks5-client", + "nym-task", "pretty_env_logger", "rand 0.8.5", "reqwest", @@ -3406,58 +3433,439 @@ dependencies = [ "serde_json", "serde_repr", "tap", - "task", "tauri", "tauri-build", - "tauri-codegen", - "tauri-macros", "tempfile", "tendermint-rpc", "thiserror", "tokio", - "topology", "ts-rs", "url", "yaml-rust", ] +[[package]] +name = "nym-contracts-common" +version = "0.2.0" +dependencies = [ + "bs58", + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-credential-storage" +version = "0.1.0" +dependencies = [ + "async-trait", + "log", + "sqlx 0.5.13", + "thiserror", + "tokio", +] + +[[package]] +name = "nym-credentials" +version = "0.1.0" +dependencies = [ + "bls12_381 0.5.0", + "cosmrs", + "nym-api-requests", + "nym-coconut-interface", + "nym-crypto", + "thiserror", + "validator-client", +] + +[[package]] +name = "nym-crypto" +version = "0.2.0" +dependencies = [ + "aes 0.8.2", + "blake3", + "bs58", + "cipher 0.4.3", + "ctr 0.9.2", + "digest 0.10.6", + "ed25519-dalek", + "generic-array 0.14.6", + "hkdf 0.12.3", + "hmac 0.12.1", + "nym-pemstore", + "nym-sphinx-types", + "rand 0.7.3", + "subtle-encoding", + "thiserror", + "x25519-dalek", +] + +[[package]] +name = "nym-dkg" +version = "0.1.0" +dependencies = [ + "bitvec", + "bls12_381 0.6.0", + "bs58", + "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-execute" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "nym-group-contract-common" +version = "0.1.0" +dependencies = [ + "cw4", + "schemars", + "serde", +] + +[[package]] +name = "nym-mixnet-contract-common" +version = "0.2.0" +dependencies = [ + "bs58", + "cosmwasm-std", + "humantime-serde", + "log", + "nym-contracts-common", + "schemars", + "serde", + "serde_json", + "serde_repr", + "thiserror", + "time 0.3.19", +] + +[[package]] +name = "nym-multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw3-fixed-multisig", + "cw4", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-network-defaults" +version = "0.1.0" +dependencies = [ + "cfg-if", + "dotenvy", + "hex-literal", + "once_cell", + "serde", + "thiserror", + "url", +] + +[[package]] +name = "nym-nonexhaustive-delayqueue" +version = "0.1.0" +dependencies = [ + "futures-core", + "slab", + "tokio", + "tokio-stream", + "tokio-util", + "wasm-timer", +] + +[[package]] +name = "nym-ordered-buffer" +version = "0.1.0" +dependencies = [ + "log", + "thiserror", +] + +[[package]] +name = "nym-pemstore" +version = "0.2.0" +dependencies = [ + "pem", +] + +[[package]] +name = "nym-service-providers-common" +version = "0.1.0" +dependencies = [ + "async-trait", + "log", + "nym-bin-common", + "nym-sphinx-anonymous-replies", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "nym-socks5-client" -version = "1.1.3" +version = "1.1.12" dependencies = [ "clap", - "client-connections", "client-core", - "completions", - "config", - "credential-storage", - "crypto", "dirs", "futures", "gateway-client", "gateway-requests", + "lazy_static", "log", - "logging", - "network-defaults", - "nymsphinx", - "ordered-buffer", - "pemstore", + "mobile-storage", + "nym-bin-common", + "nym-coconut-interface", + "nym-config", + "nym-credentials", + "nym-crypto", + "nym-network-defaults", + "nym-ordered-buffer", + "nym-pemstore", + "nym-service-providers-common", + "nym-socks5-proxy-helpers", + "nym-socks5-requests", + "nym-sphinx", + "nym-task", + "nym-topology", "pin-project", "pretty_env_logger", - "proxy-helpers", "rand 0.7.3", "serde", "serde_json", - "socks5-requests", "tap", - "task", "thiserror", "tokio", - "topology", "url", "validator-client", +] + +[[package]] +name = "nym-socks5-proxy-helpers" +version = "0.1.0" +dependencies = [ + "bytes", + "futures", + "log", + "nym-ordered-buffer", + "nym-socks5-requests", + "nym-task", + "tokio", + "tokio-util", +] + +[[package]] +name = "nym-socks5-requests" +version = "0.1.0" +dependencies = [ + "nym-service-providers-common", + "nym-sphinx-addressing", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "nym-sphinx" +version = "0.1.0" +dependencies = [ + "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-types", + "nym-topology", + "rand 0.7.3", + "rand_distr", + "thiserror", + "tokio", +] + +[[package]] +name = "nym-sphinx-acknowledgements" +version = "0.1.0" +dependencies = [ + "nym-crypto", + "nym-pemstore", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-types", + "nym-topology", + "rand 0.7.3", + "thiserror", +] + +[[package]] +name = "nym-sphinx-addressing" +version = "0.1.0" +dependencies = [ + "nym-crypto", + "nym-sphinx-types", + "serde", + "thiserror", +] + +[[package]] +name = "nym-sphinx-anonymous-replies" +version = "0.1.0" +dependencies = [ + "bs58", + "nym-crypto", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-types", + "nym-topology", + "rand 0.7.3", + "serde", + "thiserror", + "wasm-bindgen", +] + +[[package]] +name = "nym-sphinx-chunking" +version = "0.1.0" +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" +dependencies = [ + "nym-crypto", + "nym-sphinx-acknowledgements", + "nym-sphinx-addressing", + "nym-sphinx-chunking", + "nym-sphinx-forwarding", + "nym-sphinx-params", + "nym-sphinx-types", + "nym-topology", + "rand 0.7.3", + "thiserror", +] + +[[package]] +name = "nym-sphinx-forwarding" +version = "0.1.0" +dependencies = [ + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-types", +] + +[[package]] +name = "nym-sphinx-framing" +version = "0.1.0" +dependencies = [ + "bytes", + "nym-sphinx-params", + "nym-sphinx-types", + "thiserror", + "tokio-util", +] + +[[package]] +name = "nym-sphinx-params" +version = "0.1.0" +dependencies = [ + "nym-crypto", + "nym-sphinx-types", + "thiserror", +] + +[[package]] +name = "nym-sphinx-types" +version = "0.2.0" +dependencies = [ + "sphinx-packet", +] + +[[package]] +name = "nym-task" +version = "0.1.0" +dependencies = [ + "futures", + "log", + "thiserror", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "nym-topology" +version = "0.1.0" +dependencies = [ + "async-trait", + "bs58", + "log", + "nym-bin-common", + "nym-crypto", + "nym-mixnet-contract-common", + "nym-sphinx-addressing", + "nym-sphinx-types", + "rand 0.7.3", + "thiserror", +] + +[[package]] +name = "nym-vesting-contract" +version = "1.2.0-pre.1" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "cw2", + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-vesting-contract-common", + "schemars", + "semver 1.0.16", + "serde", + "thiserror", "vergen", - "version-checker", +] + +[[package]] +name = "nym-vesting-contract-common" +version = "0.2.0" +dependencies = [ + "cosmwasm-std", + "nym-contracts-common", + "nym-mixnet-contract-common", + "schemars", + "serde", ] [[package]] @@ -3467,12 +3875,12 @@ dependencies = [ "bls12_381 0.6.0", "bs58", "digest 0.9.0", - "dkg", "ff 0.11.1", - "getrandom 0.2.7", + "getrandom 0.2.8", "group 0.11.0", "itertools", - "pemstore", + "nym-dkg", + "nym-pemstore", "rand 0.8.5", "serde", "serde_derive", @@ -3480,128 +3888,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "nymsphinx" -version = "0.1.0" -dependencies = [ - "crypto", - "nymsphinx-acknowledgements", - "nymsphinx-addressing", - "nymsphinx-anonymous-replies", - "nymsphinx-chunking", - "nymsphinx-cover", - "nymsphinx-forwarding", - "nymsphinx-framing", - "nymsphinx-params", - "nymsphinx-types", - "rand 0.7.3", - "rand_distr", - "thiserror", - "tokio", - "topology", -] - -[[package]] -name = "nymsphinx-acknowledgements" -version = "0.1.0" -dependencies = [ - "crypto", - "nymsphinx-addressing", - "nymsphinx-params", - "nymsphinx-types", - "pemstore", - "rand 0.7.3", - "topology", -] - -[[package]] -name = "nymsphinx-addressing" -version = "0.1.0" -dependencies = [ - "crypto", - "nymsphinx-types", - "serde", - "thiserror", -] - -[[package]] -name = "nymsphinx-anonymous-replies" -version = "0.1.0" -dependencies = [ - "bs58", - "crypto", - "nymsphinx-addressing", - "nymsphinx-params", - "nymsphinx-types", - "rand 0.7.3", - "serde", - "thiserror", - "topology", - "wasm-bindgen", -] - -[[package]] -name = "nymsphinx-chunking" -version = "0.1.0" -dependencies = [ - "log", - "nymsphinx-addressing", - "nymsphinx-params", - "nymsphinx-types", - "rand 0.7.3", - "thiserror", -] - -[[package]] -name = "nymsphinx-cover" -version = "0.1.0" -dependencies = [ - "crypto", - "nymsphinx-acknowledgements", - "nymsphinx-addressing", - "nymsphinx-chunking", - "nymsphinx-forwarding", - "nymsphinx-params", - "nymsphinx-types", - "rand 0.7.3", - "thiserror", - "topology", -] - -[[package]] -name = "nymsphinx-forwarding" -version = "0.1.0" -dependencies = [ - "nymsphinx-addressing", - "nymsphinx-params", - "nymsphinx-types", -] - -[[package]] -name = "nymsphinx-framing" -version = "0.1.0" -dependencies = [ - "bytes", - "nymsphinx-params", - "nymsphinx-types", - "tokio-util", -] - -[[package]] -name = "nymsphinx-params" -version = "0.1.0" -dependencies = [ - "crypto", - "nymsphinx-types", -] - -[[package]] -name = "nymsphinx-types" -version = "0.1.0" -dependencies = [ - "sphinx", -] - [[package]] name = "objc" version = "0.2.7" @@ -3643,9 +3929,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "opaque-debug" @@ -3661,19 +3947,19 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "open" -version = "3.0.3" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a3100141f1733ea40b53381b0ae3117330735ef22309a190ac57b9576ea716" +checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" dependencies = [ "pathdiff", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "openssl" -version = "0.10.41" +version = "0.10.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" dependencies = [ "bitflags", "cfg-if", @@ -3702,30 +3988,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] -name = "openssl-sys" -version = "0.9.75" +name = "openssl-src" +version = "111.25.0+1.1.1t" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +checksum = "3173cd3626c43e3854b1b727422a276e568d9ec5fe8cec197822cf52cfb743d6" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" dependencies = [ "autocfg 1.1.0", "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] [[package]] -name = "ordered-buffer" -version = "0.1.0" -dependencies = [ - "log", -] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] -name = "os_str_bytes" -version = "6.3.0" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pairing" @@ -3747,11 +4042,12 @@ dependencies = [ [[package]] name = "pango" -version = "0.15.10" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" +checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" dependencies = [ "bitflags", + "gio", "glib", "libc", "once_cell", @@ -3760,14 +4056,14 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.15.10" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" +checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.2", + "system-deps", ] [[package]] @@ -3778,7 +4074,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -3788,41 +4084,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", + "parking_lot_core 0.9.7", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if", "instant", "libc", "redox_syscall", - "smallvec 1.9.0", + "smallvec 1.10.0", "winapi", ] [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec 1.9.0", - "windows-sys", + "smallvec 1.10.0", + "windows-sys 0.45.0", ] [[package]] name = "paste" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "pathdiff" @@ -3872,18 +4168,11 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" dependencies = [ - "base64", + "base64 0.13.1", "once_cell", "regex", ] -[[package]] -name = "pemstore" -version = "0.1.0" -dependencies = [ - "pem", -] - [[package]] name = "percent-encoding" version = "2.2.0" @@ -3892,9 +4181,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.3.1" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048" +checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" dependencies = [ "thiserror", "ucd-trie", @@ -3902,9 +4191,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.3.1" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502b62a6d0245378b04ffe0a7fb4f4419a4815fce813bd8a0ec89a56e07d67b1" +checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69" dependencies = [ "pest", "pest_generator", @@ -3912,9 +4201,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.3.1" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451e629bf49b750254da26132f1a5a9d11fd8a95a3df51d15c4abd1ba154cb6c" +checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202" dependencies = [ "pest", "pest_meta", @@ -3925,13 +4214,13 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.3.1" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec162c71c45e269dfc3fc2916eaeb97feab22993a21bcce4721d08cd7801a6" +checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616" dependencies = [ "once_cell", "pest", - "sha1", + "sha2 0.10.6", ] [[package]] @@ -4077,29 +4366,29 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "plist" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" +checksum = "9469799ca90293a376f68f6fcb8f11990d9cff55602cfba0ba83893c973a7f46" dependencies = [ - "base64", + "base64 0.21.0", "indexmap", "line-wrap", + "quick-xml 0.26.0", "serde", - "time 0.3.17", - "xml-rs", + "time 0.3.19", ] [[package]] name = "png" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" dependencies = [ "bitflags", "crc32fast", @@ -4109,9 +4398,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "precomputed-hash" @@ -4131,13 +4420,12 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" dependencies = [ "once_cell", - "thiserror", - "toml", + "toml_edit", ] [[package]] @@ -4166,15 +4454,15 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.19" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] @@ -4212,21 +4500,6 @@ dependencies = [ "prost", ] -[[package]] -name = "proxy-helpers" -version = "0.1.0" -dependencies = [ - "bytes", - "client-connections", - "futures", - "log", - "ordered-buffer", - "socks5-requests", - "task", - "tokio", - "tokio-util", -] - [[package]] name = "quick-error" version = "1.2.3" @@ -4249,10 +4522,19 @@ dependencies = [ ] [[package]] -name = "quote" -version = "1.0.21" +name = "quick-xml" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -4367,7 +4649,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", ] [[package]] @@ -4493,16 +4775,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", "redox_syscall", "thiserror", ] [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -4520,9 +4802,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -4535,11 +4817,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ - "base64", + "base64 0.21.0", "bytes", "encoding_rs", "futures-core", @@ -4551,10 +4833,10 @@ dependencies = [ "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log", "mime", "native-tls", + "once_cell", "percent-encoding", "pin-project-lite", "serde", @@ -4584,9 +4866,9 @@ dependencies = [ [[package]] name = "rfd" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" +checksum = "1fe3bae427b011620cf4436d5dd7405c1a86dce3dfcdc0cda12b41fd31569ac3" dependencies = [ "block", "dispatch", @@ -4594,7 +4876,6 @@ dependencies = [ "gobject-sys", "gtk-sys", "js-sys", - "lazy_static", "log", "objc", "objc-foundation", @@ -4603,7 +4884,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows 0.37.0", + "windows 0.44.0", ] [[package]] @@ -4682,7 +4963,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.14", + "semver 1.0.16", +] + +[[package]] +name = "rustix" +version = "0.36.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", ] [[package]] @@ -4691,7 +4986,7 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64", + "base64 0.13.1", "log", "ring", "sct 0.6.1", @@ -4700,9 +4995,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -4724,24 +5019,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "safemem" @@ -4760,19 +5055,18 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "schemars" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" +checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307" dependencies = [ "dyn-clone", "indexmap", @@ -4783,9 +5077,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" +checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" dependencies = [ "proc-macro2", "quote", @@ -4795,9 +5089,9 @@ dependencies = [ [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -4805,6 +5099,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" + [[package]] name = "sct" version = "0.6.1" @@ -4840,9 +5140,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.7.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", @@ -4853,9 +5153,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -4877,7 +5177,7 @@ dependencies = [ "phf_codegen", "precomputed-hash", "servo_arc", - "smallvec 1.9.0", + "smallvec 1.10.0", "thin-slice", ] @@ -4892,9 +5192,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" dependencies = [ "serde", ] @@ -4910,9 +5210,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.144" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] @@ -4928,18 +5228,18 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.7" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -4959,20 +5259,20 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ - "itoa 1.0.3", + "itoa 1.0.5", "ryu", "serde", ] [[package]] name = "serde_repr" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" dependencies = [ "proc-macro2", "quote", @@ -4986,7 +5286,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.3", + "itoa 1.0.5", "ryu", "serde", ] @@ -5066,7 +5366,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -5090,7 +5390,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -5116,9 +5416,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -5141,9 +5441,9 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg 1.1.0", ] @@ -5159,9 +5459,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" @@ -5174,45 +5474,38 @@ dependencies = [ ] [[package]] -name = "socks5-requests" -version = "0.1.0" -dependencies = [ - "nymsphinx-addressing", - "thiserror", -] - -[[package]] -name = "soup2" -version = "0.2.1" +name = "soup3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" +checksum = "82bc46048125fefd69d30b32b9d263d6556c9ffe82a7a7df181a86d912da5616" dependencies = [ "bitflags", + "futures-channel", "gio", "glib", "libc", "once_cell", - "soup2-sys", + "soup3-sys", ] [[package]] -name = "soup2-sys" -version = "0.2.0" +name = "soup3-sys" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" +checksum = "014bbeb1c4cdb30739dc181e8d98b7908f124d9555843afa89b5570aaf4ec62b" dependencies = [ - "bitflags", "gio-sys", "glib-sys", "gobject-sys", "libc", - "system-deps 5.0.0", + "system-deps", ] [[package]] -name = "sphinx" +name = "sphinx-packet" version = "0.1.0" -source = "git+https://github.com/nymtech/sphinx?rev=e05a1992522ed0afd3c6fcac160313ffc9bb306a#e05a1992522ed0afd3c6fcac160313ffc9bb306a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" dependencies = [ "aes 0.7.5", "arrayref", @@ -5240,9 +5533,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc" dependencies = [ "lock_api", ] @@ -5270,9 +5563,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87e292b4291f154971a43c3774364e2cbcaec599d3f5bf6fa9d122885dbc38a" +checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" dependencies = [ "itertools", "nom", @@ -5323,7 +5616,7 @@ dependencies = [ "hashlink 0.7.0", "hex", "indexmap", - "itoa 1.0.3", + "itoa 1.0.5", "libc", "libsqlite3-sys", "log", @@ -5333,7 +5626,7 @@ dependencies = [ "percent-encoding", "rustls 0.19.1", "sha2 0.10.6", - "smallvec 1.9.0", + "smallvec 1.10.0", "sqlformat 0.1.8", "sqlx-rt 0.5.13", "stringprep", @@ -5355,7 +5648,7 @@ dependencies = [ "bitflags", "byteorder", "bytes", - "crc 3.0.0", + "crc 3.0.1", "crossbeam-queue", "dotenvy", "either", @@ -5369,7 +5662,7 @@ dependencies = [ "hashlink 0.8.1", "hex", "indexmap", - "itoa 1.0.3", + "itoa 1.0.5", "libc", "libsqlite3-sys", "log", @@ -5377,17 +5670,17 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rustls 0.20.7", + "rustls 0.20.8", "rustls-pemfile", "sha2 0.10.6", - "smallvec 1.9.0", - "sqlformat 0.2.0", + "smallvec 1.10.0", + "sqlformat 0.2.1", "sqlx-rt 0.6.2", "stringprep", "thiserror", "tokio-stream", "url", - "webpki-roots 0.22.5", + "webpki-roots 0.22.6", ] [[package]] @@ -5398,7 +5691,7 @@ checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1" dependencies = [ "dotenv", "either", - "heck 0.4.0", + "heck 0.4.1", "once_cell", "proc-macro2", "quote", @@ -5417,7 +5710,7 @@ checksum = "b850fa514dc11f2ee85be9d055c512aa866746adfacd1cb42d867d68e6a5b0d9" dependencies = [ "dotenvy", "either", - "heck 0.4.0", + "heck 0.4.1", "once_cell", "proc-macro2", "quote", @@ -5566,9 +5859,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.100" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -5589,35 +5882,22 @@ dependencies = [ [[package]] name = "system-deps" -version = "5.0.0" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" +checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" dependencies = [ - "cfg-expr 0.9.1", - "heck 0.3.3", + "cfg-expr", + "heck 0.4.1", "pkg-config", "toml", - "version-compare 0.0.11", -] - -[[package]] -name = "system-deps" -version = "6.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709" -dependencies = [ - "cfg-expr 0.10.3", - "heck 0.4.0", - "pkg-config", - "toml", - "version-compare 0.1.0", + "version-compare", ] [[package]] name = "tao" -version = "0.15.7" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fa15735311b4816d030ff54da58560b047daca0970e1031aed5502e84231a8" +checksum = "0d021218bcb43d4bf42112b1da5f7d8454502ffb188489ba0089fe4e3e34a8f6" dependencies = [ "bitflags", "cairo-rs", @@ -5649,18 +5929,29 @@ dependencies = [ "objc", "once_cell", "parking_lot 0.12.1", - "paste", "png", "raw-window-handle", "scopeguard", "serde", + "tao-macros", "unicode-segmentation", - "uuid 1.2.2", + "uuid 1.3.0", "windows 0.39.0", "windows-implement", "x11-dl", ] +[[package]] +name = "tao-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b27a4bcc5eb524658234589bdffc7e7bfb996dbae6ce9393bfd39cb4159b445" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tap" version = "1.0.1" @@ -5678,27 +5969,14 @@ dependencies = [ "xattr", ] -[[package]] -name = "task" -version = "0.1.0" -dependencies = [ - "futures", - "log", - "thiserror", - "tokio", - "wasm-bindgen", - "wasm-bindgen-futures", -] - [[package]] name = "tauri" -version = "1.2.2" +version = "2.0.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8ea1d785ab2164373703817bff144c4610a69ad3f659becaca0e1ea004b98d8" +checksum = "f3eb6015452c545d13d2eb04166376474ed0296bc647229f798937f75d3e9908" dependencies = [ "anyhow", "attohttpc", - "base64", "cocoa", "dirs-next", "embed_plist", @@ -5708,20 +5986,22 @@ dependencies = [ "glib", "glob", "gtk", - "heck 0.4.0", + "heck 0.4.1", "http", "ignore", - "minisign-verify", + "libc", + "log", "notify-rust", "objc", "once_cell", "open", + "paste", "percent-encoding", "rand 0.8.5", "raw-window-handle", "regex", "rfd", - "semver 1.0.14", + "semver 1.0.16", "serde", "serde_json", "serde_repr", @@ -5734,27 +6014,25 @@ dependencies = [ "tauri-utils", "tempfile", "thiserror", - "time 0.3.17", "tokio", "url", - "uuid 1.2.2", + "uuid 1.3.0", "webkit2gtk", "webview2-com", "windows 0.39.0", - "zip", ] [[package]] name = "tauri-build" -version = "1.2.1" +version = "2.0.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8807c85d656b2b93927c19fe5a5f1f1f348f96c2de8b90763b3c2d561511f9b4" +checksum = "c273e6fd861c23b537b4d8d0175775e472e9f856bbd2b2c4422df80506ff8fb0" dependencies = [ "anyhow", "cargo_toml", - "heck 0.4.0", + "heck 0.4.1", "json-patch", - "semver 1.0.14", + "semver 1.0.16", "serde_json", "tauri-utils", "winres", @@ -5762,11 +6040,11 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "1.2.1" +version = "2.0.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14388d484b6b1b5dc0f6a7d6cc6433b3b230bec85eaa576adcdf3f9fafa49251" +checksum = "af7d3464109ac3b7fbff0d78f1bc6b309af49c3d6267ebace90c5a47fbac58cb" dependencies = [ - "base64", + "base64 0.13.1", "brotli", "ico", "json-patch", @@ -5775,24 +6053,25 @@ dependencies = [ "proc-macro2", "quote", "regex", - "semver 1.0.14", + "semver 1.0.16", "serde", "serde_json", "sha2 0.10.6", "tauri-utils", "thiserror", - "time 0.3.17", - "uuid 1.2.2", + "time 0.3.19", + "url", + "uuid 1.3.0", "walkdir", ] [[package]] name = "tauri-macros" -version = "1.2.1" +version = "2.0.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069319e5ecbe653a799b94b0690d9f9bf5d00f7b1d3989aa331c524d4e354075" +checksum = "969f323a5c603fe2d113dd1098b6d1a965fd92b78e7bd7f74eb0bb6d7d948183" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro2", "quote", "syn", @@ -5802,9 +6081,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "0.12.1" +version = "0.13.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c507d954d08ac8705d235bc70ec6975b9054fb95ff7823af72dbb04186596f3b" +checksum = "5c7369709ff9ac16a1235906801033804d8ea33e4d023e066f06df0c5c9592a0" dependencies = [ "gtk", "http", @@ -5815,16 +6094,16 @@ dependencies = [ "serde_json", "tauri-utils", "thiserror", - "uuid 1.2.2", + "uuid 1.3.0", "webview2-com", "windows 0.39.0", ] [[package]] name = "tauri-runtime-wry" -version = "0.12.2" +version = "0.13.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30" +checksum = "b914d691ef830dfa98658409eea9d9116240ae0c9d3eb228eed2a9c139a60916" dependencies = [ "cocoa", "gtk", @@ -5833,7 +6112,7 @@ dependencies = [ "raw-window-handle", "tauri-runtime", "tauri-utils", - "uuid 1.2.2", + "uuid 1.3.0", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -5842,14 +6121,14 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "1.2.1" +version = "2.0.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5abbc109a6eb45127956ffcc26ef0e875d160150ac16cfa45d26a6b2871686f1" +checksum = "34ad593254f413f37d8fb85a2a189869a01c17ed995c633a3c36b0e5f8a3d696" dependencies = [ "brotli", "ctor", "glob", - "heck 0.4.0", + "heck 0.4.1", "html5ever", "infer", "json-patch", @@ -5858,7 +6137,7 @@ dependencies = [ "phf 0.10.1", "proc-macro2", "quote", - "semver 1.0.14", + "semver 1.0.16", "serde", "serde_json", "serde_with", @@ -5874,7 +6153,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c58de036c4d2e20717024de2a3c4bf56c301f07b21bc8ef9b57189fce06f1f3b" dependencies = [ - "quick-xml", + "quick-xml 0.23.1", "strum", "windows 0.39.0", ] @@ -5920,7 +6199,7 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto", - "time 0.3.17", + "time 0.3.19", "zeroize", ] @@ -5953,7 +6232,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.17", + "time 0.3.19", ] [[package]] @@ -5966,7 +6245,7 @@ dependencies = [ "bytes", "flex-error", "futures", - "getrandom 0.2.7", + "getrandom 0.2.8", "http", "hyper", "hyper-proxy", @@ -5981,7 +6260,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.17", + "time 0.3.19", "tokio", "tracing", "url", @@ -6002,19 +6281,13 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" - [[package]] name = "thin-slice" version = "0.1.1" @@ -6023,18 +6296,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -6043,18 +6316,19 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", @@ -6063,11 +6337,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "53250a3b3fed8ff8fd988587d8925d26a83ac3845d9e03b220b37f34c2b8d6c2" dependencies = [ - "itoa 1.0.3", + "itoa 1.0.5", "js-sys", "serde", "time-core", @@ -6082,18 +6356,18 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "a460aeb8de6dcb0f381e1ee05f1cd56fcf5a5f6eb8187ff3d8f0b11078d38b7c" dependencies = [ "time-core", ] [[package]] name = "tokio" -version = "1.22.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg 1.1.0", "bytes", @@ -6105,14 +6379,14 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -6121,9 +6395,9 @@ dependencies = [ [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -6146,7 +6420,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.7", + "rustls 0.20.8", "tokio", "webpki 0.22.0", ] @@ -6165,9 +6439,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" dependencies = [ "futures-core", "pin-project-lite", @@ -6190,9 +6464,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -6205,26 +6479,28 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] -name = "topology" -version = "0.1.0" +name = "toml_datetime" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" + +[[package]] +name = "toml_edit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" dependencies = [ - "bs58", - "crypto", - "log", - "mixnet-contract-common", - "nymsphinx-addressing", - "nymsphinx-types", - "rand 0.7.3", - "thiserror", - "version-checker", + "indexmap", + "nom8", + "toml_datetime", ] [[package]] @@ -6235,9 +6511,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", @@ -6247,9 +6523,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -6258,9 +6534,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", "valuable", @@ -6279,16 +6555,16 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ - "ansi_term", "matchers", + "nu-ansi-term", "once_cell", "regex", "sharded-slab", - "smallvec 1.9.0", + "smallvec 1.10.0", "thread_local", "tracing", "tracing-core", @@ -6306,15 +6582,15 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ts-rs" -version = "6.2.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc59f479df54269b400dd95bc3b7e81623b3e4b9c70c8ca7125ab8341eafa64e" +checksum = "4added4070a4fdf9df03457206cd2e4b12417c8560a2954d91ffcbe60177a56a" dependencies = [ "thiserror", "ts-rs-macros", @@ -6339,7 +6615,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "bytes", "http", @@ -6355,9 +6631,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" @@ -6367,9 +6643,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy", @@ -6379,15 +6655,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -6400,9 +6676,15 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" @@ -6454,25 +6736,11 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" dependencies = [ - "getrandom 0.2.7", -] - -[[package]] -name = "validator-api-requests" -version = "0.1.0" -dependencies = [ - "bs58", - "coconut-interface", - "cosmrs", - "cosmwasm-std", - "getset", - "mixnet-contract-common", - "schemars", - "serde", + "getrandom 0.2.8", ] [[package]] @@ -6480,25 +6748,30 @@ name = "validator-client" version = "0.1.0" dependencies = [ "async-trait", - "base64", + "base64 0.13.1", "bip39", - "coconut-bandwidth-contract-common", - "coconut-dkg-common", - "coconut-interface", "colored 2.0.0", - "config", - "contracts-common", "cosmrs", "cosmwasm-std", "cw3", - "execute", + "cw4", "flate2", "futures", "itertools", "log", - "mixnet-contract-common", - "multisig-contract-common", - "network-defaults", + "nym-api-requests", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", + "nym-coconut-interface", + "nym-config", + "nym-contracts-common", + "nym-execute", + "nym-group-contract-common", + "nym-mixnet-contract-common", + "nym-multisig-contract-common", + "nym-network-defaults", + "nym-vesting-contract", + "nym-vesting-contract-common", "prost", "reqwest", "serde", @@ -6507,9 +6780,7 @@ dependencies = [ "thiserror", "tokio", "url", - "validator-api-requests", - "vesting-contract", - "vesting-contract-common", + "zeroize", ] [[package]] @@ -6526,39 +6797,26 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "5.1.17" +version = "7.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e" +checksum = "447f9238a4553957277b3ee09d80babeae0811f1b3baefb093de1c0448437a37" dependencies = [ "anyhow", "cfg-if", - "chrono", "enum-iterator", "getset", "git2", "rustc_version 0.4.0", "rustversion", "thiserror", -] - -[[package]] -name = "version-checker" -version = "0.1.0" -dependencies = [ - "semver 0.11.0", + "time 0.3.19", ] [[package]] name = "version-compare" -version = "0.0.11" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" - -[[package]] -name = "version-compare" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" [[package]] name = "version_check" @@ -6566,32 +6824,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vesting-contract" -version = "1.1.2" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "cw-storage-plus", - "mixnet-contract-common", - "schemars", - "serde", - "thiserror", - "vergen", - "vesting-contract-common", -] - -[[package]] -name = "vesting-contract-common" -version = "0.1.0" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "mixnet-contract-common", - "schemars", - "serde", -] - [[package]] name = "vte" version = "0.10.1" @@ -6679,9 +6911,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.28" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if", "js-sys", @@ -6746,9 +6978,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -6756,9 +6988,9 @@ dependencies = [ [[package]] name = "webkit2gtk" -version = "0.18.2" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" +checksum = "d8eea819afe15eb8dcdff4f19d8bfda540bae84d874c10e6f4b8faf2d6704bd1" dependencies = [ "bitflags", "cairo-rs", @@ -6774,20 +7006,18 @@ dependencies = [ "javascriptcore-rs", "libc", "once_cell", - "soup2", + "soup3", "webkit2gtk-sys", ] [[package]] name = "webkit2gtk-sys" -version = "0.18.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" +checksum = "d0ac7a95ddd3fdfcaf83d8e513b4b1ad101b95b413b6aa6662ed95f284fc3d5b" dependencies = [ - "atk-sys", "bitflags", "cairo-sys-rs", - "gdk-pixbuf-sys", "gdk-sys", "gio-sys", "glib-sys", @@ -6795,10 +7025,9 @@ dependencies = [ "gtk-sys", "javascriptcore-rs-sys", "libc", - "pango-sys", "pkg-config", - "soup2-sys", - "system-deps 6.0.2", + "soup3-sys", + "system-deps", ] [[package]] @@ -6832,9 +7061,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki 0.22.0", ] @@ -6908,32 +7137,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec" -dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", -] - -[[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" @@ -6948,6 +7151,15 @@ dependencies = [ "windows_x86_64_msvc 0.39.0", ] +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-bindgen" version = "0.39.0" @@ -6976,15 +7188,41 @@ checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 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", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", ] [[package]] @@ -6994,22 +7232,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" [[package]] -name = "windows_aarch64_msvc" -version = "0.32.0" +name = "windows_aarch64_gnullvm" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" - -[[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" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" @@ -7018,22 +7244,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" [[package]] -name = "windows_i686_gnu" -version = "0.32.0" +name = "windows_aarch64_msvc" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" - -[[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" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" @@ -7042,22 +7256,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" [[package]] -name = "windows_i686_msvc" -version = "0.32.0" +name = "windows_i686_gnu" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" - -[[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" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" @@ -7066,22 +7268,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" [[package]] -name = "windows_x86_64_gnu" -version = "0.32.0" +name = "windows_i686_msvc" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" - -[[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" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" @@ -7090,22 +7280,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" [[package]] -name = "windows_x86_64_msvc" -version = "0.32.0" +name = "windows_x86_64_gnu" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" +name = "windows_x86_64_gnullvm" +version = "0.42.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" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" @@ -7113,6 +7297,12 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + [[package]] name = "winreg" version = "0.10.1" @@ -7133,11 +7323,11 @@ dependencies = [ [[package]] name = "wry" -version = "0.23.4" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c1ad8e2424f554cc5bdebe8aa374ef5b433feff817aebabca0389961fc7ef98" +checksum = "8f8052d7e38efb90235f1895d43904fc152e1efd96383fe57e30ea596738fa3b" dependencies = [ - "base64", + "base64 0.13.1", "block", "cocoa", "core-graphics", @@ -7158,7 +7348,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.6", - "soup2", + "soup3", "tao", "thiserror", "url", @@ -7180,9 +7370,9 @@ dependencies = [ [[package]] name = "x11" -version = "2.20.0" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ae97874a928d821b061fce3d1fc52f08071dd53c89a6102bc06efcac3b2908" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" dependencies = [ "libc", "pkg-config", @@ -7190,12 +7380,12 @@ dependencies = [ [[package]] name = "x11-dl" -version = "2.20.0" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c83627bc137605acc00bb399c7b908ef460b621fc37c953db2b09f88c449ea6" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ - "lazy_static", "libc", + "once_cell", "pkg-config", ] @@ -7219,12 +7409,6 @@ dependencies = [ "libc", ] -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - [[package]] name = "yaml-rust" version = "0.4.5" @@ -7245,23 +7429,12 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] - -[[package]] -name = "zip" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080" -dependencies = [ - "byteorder", - "crc32fast", - "crossbeam-utils", -] diff --git a/nym-connect/mobile/src-tauri/Cargo.toml b/nym-connect/mobile/src-tauri/Cargo.toml new file mode 100644 index 0000000000..8614dd7f0c --- /dev/null +++ b/nym-connect/mobile/src-tauri/Cargo.toml @@ -0,0 +1,74 @@ +[package] +name = "nym-connect" +version = "1.1.9" +description = "nym-connect for Mobile" +authors = ["Nym Technologies SA"] +license = "" +repository = "" +default-run = "nym-connect" +edition = "2021" +build = "src/build.rs" +rust-version = "1.58" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2.0.0-alpha.1", features = [] } +# tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = [] } + +[dependencies] +anyhow = "1.0" +bip39 = "1.0" +chrono = "0.4" +dirs = "4.0" +eyre = "0.6.5" +fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"} +futures = "0.3" +fern = { version = "0.6.1", features = ["colored"] } +itertools = "0.10.5" +log = { version = "0.4", features = ["serde"] } +pretty_env_logger = "0.4.0" +rand = "0.8" +reqwest = { version = "0.11", features = ["json", "socks"] } +rust-embed = { version = "6.4.2", features = ["include-exclude"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_repr = "0.1" +tap = "1.0.1" +# tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] } +tauri = { version = "2.0.0-alpha.3", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] } +tendermint-rpc = "0.23.0" +thiserror = "1.0" +tokio = { version = "1.24.1", features = ["sync", "time"] } +url = "2.2" +yaml-rust = "0.4" + +client-core = { path = "../../../clients/client-core" } +nym-api-requests = { path = "../../../nym-api/nym-api-requests" } +nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common"} +nym-config-common = { path = "../../../common/config", package = "nym-config" } +nym-crypto = { path = "../../../common/crypto" } +nym-bin-common = { path = "../../../common/bin-common"} +nym-socks5-client = { path = "../../../clients/socks5", features = ["mobile"], default-features = false } +nym-task = { path = "../../../common/task" } + +[dev-dependencies] +ts-rs = "6.1.2" +tempfile = "3.3.0" + +[features] +default = ["custom-protocol"] +custom-protocol = ["tauri/custom-protocol"] + +# [profile.dev] +# strip = true +# opt-level = "s" +# lto = true + +[profile.release] +strip = true +opt-level = "s" +lto = true diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/.editorconfig b/nym-connect/mobile/src-tauri/gen/android/nym_connect/.editorconfig new file mode 100644 index 0000000000..ebe51d3bfa --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/.gitignore b/nym-connect/mobile/src-tauri/gen/android/nym_connect/.gitignore new file mode 100644 index 0000000000..edf9888947 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +build +/captures +.externalNativeBuild +.cxx +local.properties +app/src/main/jniLibs/** diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/.gitignore b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/.gitignore new file mode 100644 index 0000000000..1b13319aad --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/.gitignore @@ -0,0 +1 @@ +/src/main/java/net/nymtech/nym_connect/generated \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build.gradle.kts b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build.gradle.kts new file mode 100644 index 0000000000..3b092d967a --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build.gradle.kts @@ -0,0 +1,112 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("rustPlugin") +} + +android { + compileSdk = 33 + defaultConfig { + manifestPlaceholders["usesCleartextTraffic"] = "false" + applicationId = "net.nymtech.nym_connect" + minSdk = 24 + targetSdk = 33 + versionCode = 1 + versionName = "1.1.9" + } + sourceSets.getByName("main") { + // Vulkan validation layers + val ndkHome = System.getenv("NDK_HOME") + jniLibs.srcDir("${ndkHome}/sources/third_party/vulkan/src/build-android/jniLibs") + } + buildTypes { + getByName("debug") { + manifestPlaceholders["usesCleartextTraffic"] = "true" + isDebuggable = true + isJniDebuggable = true + isMinifyEnabled = false + packagingOptions { + jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so") + + jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so") + + jniLibs.keepDebugSymbols.add("*/x86/*.so") + + jniLibs.keepDebugSymbols.add("*/x86_64/*.so") + } + } + getByName("release") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + } + } + flavorDimensions.add("abi") + productFlavors { + create("universal") { + val abiList = findProperty("abiList") as? String + + dimension = "abi" + ndk { + abiFilters += abiList?.split(",")?.map { it.trim() } ?: listOf( "arm64-v8a", "armeabi-v7a", "x86", "x86_64", + ) + } + } + create("arm64") { + dimension = "abi" + ndk { + abiFilters += listOf("arm64-v8a") + } + } + + create("arm") { + dimension = "abi" + ndk { + abiFilters += listOf("armeabi-v7a") + } + } + + create("x86") { + dimension = "abi" + ndk { + abiFilters += listOf("x86") + } + } + + create("x86_64") { + dimension = "abi" + ndk { + abiFilters += listOf("x86_64") + } + } + } + + assetPacks += mutableSetOf() + namespace = "net.nymtech.nym_connect" +} + +rust { + rootDirRel = "../../../../" + targets = listOf("aarch64", "armv7", "i686", "x86_64") + arches = listOf("arm64", "arm", "x86", "x86_64") +} + +dependencies { + implementation("androidx.webkit:webkit:1.5.0") + implementation("androidx.appcompat:appcompat:1.5.1") + implementation("com.google.android.material:material:1.7.0") + implementation("androidx.core:core-splashscreen:1.0.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.4") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") +} + +afterEvaluate { + android.applicationVariants.all { + tasks["mergeUniversalReleaseJniLibFolders"].dependsOn(tasks["rustBuildRelease"]) + tasks["mergeUniversalDebugJniLibFolders"].dependsOn(tasks["rustBuildDebug"]) + productFlavors.filter{ it.name != "universal" }.forEach { _ -> + val archAndBuildType = name.capitalize() + tasks["merge${archAndBuildType}JniLibFolders"].dependsOn(tasks["rustBuild${archAndBuildType}"]) + } + } +} diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/proguard-rules.pro b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/AndroidManifest.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..1c575ab17a --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/MainActivity.kt b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/MainActivity.kt new file mode 100644 index 0000000000..1162a2e43b --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/MainActivity.kt @@ -0,0 +1,3 @@ +package net.nymtech.nym_connect + +class MainActivity : TauriActivity() diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/generated/.gitkeep b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/java/net/nymtech/nym_connect/generated/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000..2b068d1146 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable/ic_launcher_background.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..07d5da9cbf --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/layout/activity_main.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..4fc244418b --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000..036d09bc5f --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000..036d09bc5f --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..4974445c9a Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..483ace7edf Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000..59b2f1db44 Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..307fb6aab9 Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..e634763b42 Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000..37a8d502c8 Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..5b412e78a0 Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..580c767c54 Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..51a588fdee Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..65cefcb196 Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..312d818bbe Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..5070e583a0 Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..18252905f8 Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..514ec36652 Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..83e115a87d Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values-night/themes.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000000..894b25a6c9 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values-night/themes.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/colors.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/colors.xml new file mode 100644 index 0000000000..0018ee4a8a --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/colors.xml @@ -0,0 +1,9 @@ + + + #1d2125 + #424242 + #4caf50 + #1b5e20 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/ic_launcher_background.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000000..a3b9258141 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #1D2125 + \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/strings.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000..74cf5d6f34 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Nym Connect + \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/themes.xml b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/themes.xml new file mode 100644 index 0000000000..894b25a6c9 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/app/src/main/res/values/themes.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/build.gradle.kts b/nym-connect/mobile/src-tauri/gen/android/nym_connect/build.gradle.kts new file mode 100644 index 0000000000..8c6fe5848c --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/build.gradle.kts @@ -0,0 +1,25 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:7.3.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +tasks.register("clean").configure { + delete("build") +} + diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/build.gradle.kts b/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/build.gradle.kts new file mode 100644 index 0000000000..45981f7511 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + `kotlin-dsl` +} + +gradlePlugin { + plugins { + create("pluginsForCoolKids") { + id = "rustPlugin" + implementationClass = "net.nymtech.RustPlugin" + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + compileOnly(gradleApi()) + implementation("com.android.tools.build:gradle:7.3.1") +} + diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/BuildTask.kt b/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/BuildTask.kt new file mode 100644 index 0000000000..5f63ba21c5 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/BuildTask.kt @@ -0,0 +1,54 @@ +package net.nymtech + +import java.io.File +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.logging.LogLevel +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction + +open class BuildTask : DefaultTask() { + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + var rootDirRel: File? = null + + @Input + var target: String? = null + + @Input + var release: Boolean? = null + + @TaskAction + fun build() { + val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") + val target = target ?: throw GradleException("target cannot be null") + val release = release ?: throw GradleException("release cannot be null") + val nodeExecutable = System.getenv("NODE_TAURI_CLI") + val home = (System.getenv("HOME") ?: "") + val cargoHome = (System.getenv("CARGO_HOME") ?: "$home/.cargo") + val rustExecutable = "$cargoHome/bin/cargo-tauri" + val tauriCli = when { + !nodeExecutable.isNullOrEmpty() && File(nodeExecutable).isFile() -> nodeExecutable + File(rustExecutable).isFile() -> rustExecutable + else -> throw GradleException("couldn't find tauri-cli executable") + } + println("gradle Rust plugin, using tauri cli executable: $tauriCli") + project.exec { + workingDir(File(project.projectDir, rootDirRel.path)) + executable(tauriCli) + args(listOf("android", "android-studio-script")) + if (project.logger.isEnabled(LogLevel.DEBUG)) { + args("-vv") + } else if (project.logger.isEnabled(LogLevel.INFO)) { + args("-v") + } + if (release) { + args("--release") + } + args(listOf("--target", target)) + }.assertNormalExitValue() + } +} \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/RustPlugin.kt b/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/RustPlugin.kt new file mode 100644 index 0000000000..5e8dcdb3ac --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/buildSrc/src/main/java/net/nymtech/nym_connect/kotlin/RustPlugin.kt @@ -0,0 +1,59 @@ +package net.nymtech + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project +import java.io.File +import java.util.* + +const val TASK_GROUP = "rust" + +open class Config { + var rootDirRel: String? = null + var targets: List? = null + var arches: List? = null +} + +open class RustPlugin : Plugin { + private lateinit var config: Config + + override fun apply(project: Project) { + config = project.extensions.create("rust", Config::class.java) + project.afterEvaluate { + if (config.targets == null) { + throw GradleException("targets cannot be null") + } + if (config.arches == null) { + throw GradleException("arches cannot be null") + } + for (profile in listOf("debug", "release")) { + val profileCapitalized = profile.capitalize(Locale.ROOT) + val buildTask = project.tasks.maybeCreate( + "rustBuild$profileCapitalized", + DefaultTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for all targets" + } + for (targetPair in config.targets!!.withIndex()) { + val targetName = targetPair.value + val targetArch = config.arches!![targetPair.index] + val targetArchCapitalized = targetArch.capitalize(Locale.ROOT) + val targetBuildTask = project.tasks.maybeCreate( + "rustBuild$targetArchCapitalized$profileCapitalized", + BuildTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for $targetArch" + rootDirRel = config.rootDirRel?.let { File(it) } + target = targetName + release = profile == "release" + } + buildTask.dependsOn(targetBuildTask) + project.tasks.findByName("preBuild")?.mustRunAfter(targetBuildTask) + } + } + } + } +} diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle.properties b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle.properties new file mode 100644 index 0000000000..cd0519bb2a --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.jar b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..e708b1c023 Binary files /dev/null and b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.jar differ diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.properties b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..de8c362b62 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue May 10 19:22:52 CST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew new file mode 100755 index 0000000000..4f906e0c81 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew.bat b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew.bat new file mode 100644 index 0000000000..107acd32c4 --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/nym-connect/mobile/src-tauri/gen/android/nym_connect/settings.gradle b/nym-connect/mobile/src-tauri/gen/android/nym_connect/settings.gradle new file mode 100644 index 0000000000..e7b4def49c --- /dev/null +++ b/nym-connect/mobile/src-tauri/gen/android/nym_connect/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/nym-connect/mobile/src-tauri/icons/128x128.png b/nym-connect/mobile/src-tauri/icons/128x128.png new file mode 100644 index 0000000000..12daccf30f Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/128x128.png differ diff --git a/nym-connect/mobile/src-tauri/icons/128x128@2x.png b/nym-connect/mobile/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000000..d98b024d26 Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/128x128@2x.png differ diff --git a/nym-connect/mobile/src-tauri/icons/32x32.png b/nym-connect/mobile/src-tauri/icons/32x32.png new file mode 100644 index 0000000000..5463a8ff1c Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/32x32.png differ diff --git a/nym-connect/mobile/src-tauri/icons/Square107x107Logo.png b/nym-connect/mobile/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000000..468b6cf29b Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/Square107x107Logo.png differ diff --git a/nym-connect/mobile/src-tauri/icons/Square142x142Logo.png b/nym-connect/mobile/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000000..ce58b768ca Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/Square142x142Logo.png differ diff --git a/nym-connect/mobile/src-tauri/icons/Square150x150Logo.png b/nym-connect/mobile/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000000..b6bc59dfcf Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/Square150x150Logo.png differ diff --git a/nym-connect/mobile/src-tauri/icons/Square284x284Logo.png b/nym-connect/mobile/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000000..26311d4604 Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/Square284x284Logo.png differ diff --git a/nym-connect/mobile/src-tauri/icons/Square30x30Logo.png b/nym-connect/mobile/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000000..8916d14e49 Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/Square30x30Logo.png differ diff --git a/nym-connect/mobile/src-tauri/icons/Square310x310Logo.png b/nym-connect/mobile/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000000..6fd3c36def Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/Square310x310Logo.png differ diff --git a/nym-connect/mobile/src-tauri/icons/Square44x44Logo.png b/nym-connect/mobile/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000000..e161bd7c4a Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/Square44x44Logo.png differ diff --git a/nym-connect/mobile/src-tauri/icons/Square71x71Logo.png b/nym-connect/mobile/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000000..45b5126bf6 Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/Square71x71Logo.png differ diff --git a/nym-connect/mobile/src-tauri/icons/Square89x89Logo.png b/nym-connect/mobile/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000000..386c8bf119 Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/Square89x89Logo.png differ diff --git a/nym-connect/mobile/src-tauri/icons/StoreLogo.png b/nym-connect/mobile/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000000..692f1a03f2 Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/StoreLogo.png differ diff --git a/nym-connect/mobile/src-tauri/icons/icon.icns b/nym-connect/mobile/src-tauri/icons/icon.icns new file mode 100644 index 0000000000..129da9bf18 Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/icon.icns differ diff --git a/nym-connect/mobile/src-tauri/icons/icon.ico b/nym-connect/mobile/src-tauri/icons/icon.ico new file mode 100644 index 0000000000..6164f98f9c Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/icon.ico differ diff --git a/nym-connect/mobile/src-tauri/icons/icon.png b/nym-connect/mobile/src-tauri/icons/icon.png new file mode 100644 index 0000000000..fa552c309a Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/icon.png differ diff --git a/nym-connect/mobile/src-tauri/icons/tray_icon.ico b/nym-connect/mobile/src-tauri/icons/tray_icon.ico new file mode 100644 index 0000000000..25785da3d9 Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/tray_icon.ico differ diff --git a/nym-connect/mobile/src-tauri/icons/tray_icon.png b/nym-connect/mobile/src-tauri/icons/tray_icon.png new file mode 100644 index 0000000000..afcb965219 Binary files /dev/null and b/nym-connect/mobile/src-tauri/icons/tray_icon.png differ diff --git a/nym-connect/mobile/src-tauri/src/build.rs b/nym-connect/mobile/src-tauri/src/build.rs new file mode 100644 index 0000000000..261851f6b6 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build(); +} diff --git a/nym-connect/mobile/src-tauri/src/config/mod.rs b/nym-connect/mobile/src-tauri/src/config/mod.rs new file mode 100644 index 0000000000..7d1c3f0a99 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/config/mod.rs @@ -0,0 +1,155 @@ +use crate::{ + error::{BackendError, Result}, + state::State, +}; +use client_core::{client::key_manager::KeyManager, config::Config as BaseConfig}; +use nym_config_common::NymConfig; +use nym_crypto::asymmetric::identity; +use nym_socks5::client::config::Config as Socks5Config; +use std::path::PathBuf; +use std::sync::Arc; +use tap::TapFallible; +use tokio::sync::RwLock; + +static SOCKS5_CONFIG_ID: &str = "nym-connect"; + +pub fn socks5_config_id_appended_with(gateway_id: &str) -> Result { + use std::fmt::Write as _; + let mut id = SOCKS5_CONFIG_ID.to_string(); + write!(id, "-{gateway_id}")?; + Ok(id) +} + +#[tauri::command] +pub async fn get_config_id(state: tauri::State<'_, Arc>>) -> Result { + state.read().await.get_config_id() +} + +#[tauri::command] +pub fn get_config_file_location() -> Result { + Err(BackendError::CouldNotGetConfigFilename) +} + +#[derive(Debug)] +pub struct Config { + pub socks5: Socks5Config, +} + +impl Config { + pub fn new>(id: S, provider_mix_address: S) -> Self { + Config { + socks5: Socks5Config::new(id, provider_mix_address), + } + } + + #[allow(unused)] + pub fn new_with_port>(id: S, provider_mix_address: S, port: u16) -> Self { + Config { + socks5: Socks5Config::new(id, provider_mix_address).with_port(port), + } + } + + pub fn get_socks5(&self) -> &Socks5Config { + &self.socks5 + } + + #[allow(unused)] + pub fn get_socks5_mut(&mut self) -> &mut Socks5Config { + &mut self.socks5 + } + + pub fn get_base(&self) -> &BaseConfig { + self.socks5.get_base() + } + + pub fn get_base_mut(&mut self) -> &mut BaseConfig { + self.socks5.get_base_mut() + } + + pub async fn init( + service_provider: &str, + chosen_gateway_id: &str, + ) -> Result<(Config, KeyManager)> { + log::info!("Initialising..."); + + let service_provider = service_provider.to_owned(); + let chosen_gateway_id = chosen_gateway_id.to_owned(); + + // The client initialization was originally not written for this use case, so there are + // lots of ways it can panic. Until we have proper error handling in the init code for the + // clients we'll catch any panics here by spawning a new runtime in a separate thread. + let (config, keys) = std::thread::spawn(move || { + tokio::runtime::Runtime::new() + .expect("Failed to create tokio runtime") + .block_on( + async move { init_socks5_config(service_provider, chosen_gateway_id).await }, + ) + }) + .join() + .map_err(|_| BackendError::InitializationPanic)??; + + log::info!("Configuration saved 🚀"); + Ok((config, keys)) + } +} + +pub async fn init_socks5_config( + provider_address: String, + chosen_gateway_id: String, +) -> Result<(Config, KeyManager)> { + log::trace!("Initialising client..."); + let mut config = Config::new(SOCKS5_CONFIG_ID, &provider_address); + + if let Ok(raw_validators) = std::env::var(nym_config_common::defaults::var_names::NYM_API) { + config + .get_base_mut() + .set_custom_nym_apis(nym_config_common::parse_urls(&raw_validators)); + } + + let nym_api_endpoints = config.get_base().get_nym_api_endpoints(); + + let chosen_gateway_id = identity::PublicKey::from_base58_string(chosen_gateway_id) + .map_err(|_| BackendError::UnableToParseGateway)?; + + let mut key_manager = client_core::init::new_client_keys(); + + // Setup gateway and register a new key each time + let gateway = client_core::init::register_with_gateway( + &mut key_manager, + nym_api_endpoints, + Some(chosen_gateway_id), + false, + ) + .await?; + + config.get_base_mut().set_gateway_endpoint(gateway); + + print_saved_config(&config); + + let address = *key_manager.identity_keypair().public_key(); + log::info!("The address of this client is: {}", address); + + Ok((config, key_manager)) +} + +fn print_saved_config(config: &Config) { + log::info!( + "Saved configuration file to {:?}", + config.get_socks5().get_config_file_save_location() + ); + log::info!("Gateway id: {}", config.get_base().get_gateway_id()); + log::info!("Gateway owner: {}", config.get_base().get_gateway_owner()); + log::info!( + "Gateway listener: {}", + config.get_base().get_gateway_listener() + ); + log::info!( + "Service provider address: {}", + config.get_socks5().get_provider_mix_address() + ); + log::info!( + "Service provider port: {}", + config.get_socks5().get_listening_port() + ); + log::info!("Client configuration completed."); +} diff --git a/nym-connect/mobile/src-tauri/src/error.rs b/nym-connect/mobile/src-tauri/src/error.rs new file mode 100644 index 0000000000..9343660cbf --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/error.rs @@ -0,0 +1,86 @@ +use client_core::error::ClientCoreError; +use serde::{Serialize, Serializer}; +use thiserror::Error; + +#[allow(unused)] +#[derive(Error, Debug)] +pub enum BackendError { + #[error("{source}")] + ReqwestError { + #[from] + source: reqwest::Error, + }, + #[error("I/O error: {source}")] + IoError { + #[from] + source: std::io::Error, + }, + #[error("string formatting error: {source}")] + FmtError { + #[from] + source: std::fmt::Error, + }, + #[error("tauri error: {source}")] + TauriError { + #[from] + source: tauri::Error, + }, + #[error("{source}")] + TauriApiError { + #[from] + source: tauri::api::Error, + }, + #[error("{source}")] + SerdeJsonError { + #[from] + source: serde_json::Error, + }, + #[error("{source}")] + ClientCoreError { + #[from] + source: ClientCoreError, + }, + + #[error("could not send disconnect signal to the SOCKS5 client")] + CoundNotSendDisconnectSignal, + #[error("no service provider set")] + NoServiceProviderSet, + #[error("no gateway provider set")] + NoGatewaySet, + #[error("initialization failed with a panic")] + InitializationPanic, + #[error("could not get config id before gateway is set")] + CouldNotGetIdWithoutGateway, + #[error("could not initialize without gateway set")] + CouldNotInitWithoutGateway, + #[error("could not initialize without service provider set")] + CouldNotInitWithoutServiceProvider, + #[error("could not get file name")] + CouldNotGetFilename, + #[error("could not get config file location")] + CouldNotGetConfigFilename, + #[error("could not load existing gateway configuration")] + CouldNotLoadExistingGatewayConfiguration(std::io::Error), + #[error("unable to open a new window")] + NewWindowError, + #[error("unable to parse the specified gateway")] + UnableToParseGateway, + + #[error("HTTP get request failed: {status_code}")] + RequestFail { + url: reqwest::Url, + status_code: reqwest::StatusCode, + }, +} + +impl Serialize for BackendError { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +// Local crate level Result alias +pub(crate) type Result = std::result::Result; diff --git a/nym-connect/mobile/src-tauri/src/events.rs b/nym-connect/mobile/src-tauri/src/events.rs new file mode 100644 index 0000000000..cf277a90f9 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/events.rs @@ -0,0 +1,71 @@ +use std::sync::Arc; + +use client_core::error::ClientCoreStatusMessage; +use nym_task::manager::TaskStatus; +use tauri::async_runtime::RwLock; + +use crate::{ + state::{GatewayConnectivity, State}, + tasks, +}; + +#[derive(Clone, serde::Serialize)] +struct Payload { + title: String, + message: String, +} + +impl Payload { + fn new(title: String, message: String) -> Self { + Self { title, message } + } +} + +pub fn emit_event(event: &str, title: &str, msg: &str, window: &tauri::Window) { + if let Err(err) = window.emit(event, Payload::new(title.into(), msg.into())) { + log::error!("Failed to emit tauri event: {err}"); + } +} + +pub fn emit_status_event(event: &str, msg: &T, window: &tauri::Window) { + if let Err(err) = window.emit(event, Payload::new("SOCKS5 update".into(), msg.to_string())) { + log::error!("Failed to emit tauri event: {err}"); + } +} + +pub async fn handle_task_status( + task_status: &TaskStatus, + state: &Arc>, + window: &tauri::Window, +) { + match task_status { + TaskStatus::Ready => { + { + let mut state_w = state.write().await; + state_w.mark_connected(window); + } + + emit_status_event("socks5-connected-event", task_status, window); + tasks::start_connection_check(state.clone(), window.clone()); + } + } +} + +pub async fn handle_client_status_message( + client_status_message: &ClientCoreStatusMessage, + state: &Arc>, + window: &tauri::Window, +) { + // TODO: use this instead once we change on the frontend too + let _event_name = match client_status_message { + ClientCoreStatusMessage::GatewayIsSlow | ClientCoreStatusMessage::GatewayIsVerySlow => { + "socks5-gateway-status" + } + }; + + if let Ok(connectivity) = GatewayConnectivity::try_from(client_status_message) { + state.write().await.set_gateway_connectivity(connectivity); + } + + emit_status_event("socks5-status-event", client_status_message, window); +} diff --git a/nym-connect/mobile/src-tauri/src/lib.rs b/nym-connect/mobile/src-tauri/src/lib.rs new file mode 100644 index 0000000000..5fe3ef2552 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/lib.rs @@ -0,0 +1,93 @@ +// Remove me after we've tidied up +#![allow(dead_code)] +#![allow(unused_imports)] + +use nym_config_common::defaults::setup_env; +use std::sync::Arc; +use tauri::{App, Manager}; +use tokio::sync::RwLock; + +pub mod config; +mod error; +mod events; +mod logging; +mod models; +pub mod operations; +mod state; +mod tasks; + +#[cfg(desktop)] +mod menu; +#[cfg(desktop)] +mod window; + +pub use state::State; + +#[cfg(mobile)] +mod mobile; +#[cfg(mobile)] +pub use mobile::*; + +pub type SetupHook = Box Result<(), Box> + Send>; + +#[derive(Default)] +pub struct AppBuilder { + setup: Option, +} + +impl AppBuilder { + pub fn new() -> Self { + Self::default() + } + + #[must_use] + pub fn setup(mut self, setup: F) -> Self + where + F: FnOnce(&mut App) -> Result<(), Box> + Send + 'static, + { + self.setup.replace(Box::new(setup)); + self + } + + pub fn run(self) { + setup_env(None); + + println!("Starting up***"); + + // As per breaking change description here + // https://github.com/tauri-apps/tauri/blob/feac1d193c6d618e49916ad0707201f43d5cdd36/tooling/bundler/CHANGELOG.md + if let Err(error) = fix_path_env::fix() { + log::warn!("Failed to fix PATH: {error}"); + } + + let setup = self.setup; + tauri::Builder::default() + .manage(Arc::new(RwLock::new(State::default()))) + .invoke_handler(tauri::generate_handler![ + crate::config::get_config_file_location, + crate::config::get_config_id, + crate::operations::connection::connect::get_gateway, + crate::operations::connection::connect::get_service_provider, + crate::operations::connection::connect::set_gateway, + crate::operations::connection::connect::set_service_provider, + crate::operations::connection::connect::start_connecting, + crate::operations::connection::disconnect::start_disconnecting, + crate::operations::connection::status::get_connection_health_check_status, + crate::operations::connection::status::get_connection_status, + crate::operations::connection::status::get_gateway_connection_status, + crate::operations::connection::status::start_connection_health_check_task, + crate::operations::directory::get_services, + crate::operations::directory::get_gateways_detailed, + crate::operations::export::export_keys, + ]) + .setup(move |app| { + if let Some(setup) = setup { + logging::setup_logging(app.app_handle())?; + (setup)(app)?; + } + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); + } +} diff --git a/nym-connect/src-tauri/src/logging.rs b/nym-connect/mobile/src-tauri/src/logging.rs similarity index 93% rename from nym-connect/src-tauri/src/logging.rs rename to nym-connect/mobile/src-tauri/src/logging.rs index c805411107..804c1f2dae 100644 --- a/nym-connect/src-tauri/src/logging.rs +++ b/nym-connect/mobile/src-tauri/src/logging.rs @@ -1,12 +1,17 @@ use std::str::FromStr; -use fern::colors::ColoredLevelConfig; +use fern::colors::{Color, ColoredLevelConfig}; use serde::Serialize; use serde_repr::{Deserialize_repr, Serialize_repr}; use tauri::Manager; pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerError> { - let colors = ColoredLevelConfig::new(); + let colors = ColoredLevelConfig::new() + .trace(Color::Magenta) + .debug(Color::Blue) + .info(Color::Green) + .warn(Color::Yellow) + .error(Color::Red); let base_config = fern::Dispatch::new() .level(global_level()) .filter_lowlevel_external_components() diff --git a/nym-connect/mobile/src-tauri/src/main.rs b/nym-connect/mobile/src-tauri/src/main.rs new file mode 100644 index 0000000000..a36abeaca1 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/main.rs @@ -0,0 +1,16 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use nym_connect::AppBuilder; + +fn main() { + // As per breaking change description here + // https://github.com/tauri-apps/tauri/blob/feac1d193c6d618e49916ad0707201f43d5cdd36/tooling/bundler/CHANGELOG.md + //if let Err(error) = fix_path_env::fix() { + // log::warn!("Failed to fix PATH: {error}"); + //} + + AppBuilder::new().run(); +} diff --git a/nym-connect/mobile/src-tauri/src/menu.rs b/nym-connect/mobile/src-tauri/src/menu.rs new file mode 100644 index 0000000000..007d34d68f --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/menu.rs @@ -0,0 +1,62 @@ +use tauri::{ + AppHandle, CustomMenuItem, Menu, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu, + SystemTrayMenuItem, Wry, +}; + +use crate::window::window_toggle; + +pub const SHOW_LOG_WINDOW: &str = "show_log_window"; +pub const CLEAR_STORAGE: &str = "clear_storage"; + +pub trait AddDefaultSubmenus { + fn add_default_app_submenus(self) -> Self; +} + +impl AddDefaultSubmenus for Menu { + fn add_default_app_submenus(self) -> Self { + let submenu = Submenu::new( + "Help", + Menu::new().add_item(CustomMenuItem::new(SHOW_LOG_WINDOW, "Show logs")), + ); + self.add_submenu(submenu) + } +} + +pub const TRAY_MENU_QUIT: &str = "quit"; +pub const TRAY_MENU_SHOW_HIDE: &str = "show-hide"; +pub const TRAY_MENU_CONNECTION: &str = "connection"; + +pub(crate) fn create_tray_menu() -> SystemTray { + let quit = CustomMenuItem::new(TRAY_MENU_QUIT, "Quit"); + let hide = CustomMenuItem::new(TRAY_MENU_SHOW_HIDE, "Hide"); + let connection = CustomMenuItem::new(TRAY_MENU_CONNECTION, "Connect"); + let tray_menu = SystemTrayMenu::new() + .add_item(hide) + .add_item(connection) + .add_native_item(SystemTrayMenuItem::Separator) + .add_item(quit); + + SystemTray::new().with_menu(tray_menu) +} + +pub(crate) fn tray_menu_event_handler(app: &AppHandle, event: SystemTrayEvent) { + match event { + SystemTrayEvent::LeftClick { position, size, .. } => { + println!("Event {position:?} {size:?}"); + } + SystemTrayEvent::MenuItemClick { id, .. } => { + println!("Event {id}"); + match id.as_str() { + TRAY_MENU_SHOW_HIDE => { + window_toggle(app); + } + TRAY_MENU_QUIT => { + // TODO: add disconnecting first + app.exit(0); + } + _ => {} + } + } + _ => {} + } +} diff --git a/nym-connect/mobile/src-tauri/src/mobile.rs b/nym-connect/mobile/src-tauri/src/mobile.rs new file mode 100644 index 0000000000..d231976e68 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/mobile.rs @@ -0,0 +1,4 @@ +#[tauri::mobile_entry_point] +fn main() { + super::AppBuilder::new().run(); +} diff --git a/nym-connect/mobile/src-tauri/src/models/mod.rs b/nym-connect/mobile/src-tauri/src/models/mod.rs new file mode 100644 index 0000000000..70b408dc7b --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/models/mod.rs @@ -0,0 +1,116 @@ +use core::fmt; + +use serde::{Deserialize, Serialize}; + +use crate::state::GatewayConnectivity; + +#[cfg_attr(test, derive(ts_rs::TS))] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] +pub struct ConnectResult { + pub address: String, +} + +#[cfg_attr(test, derive(ts_rs::TS))] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] +pub struct DisconnectResult { + pub success: bool, +} + +#[cfg_attr(test, derive(ts_rs::TS))] +#[cfg_attr(test, ts(rename_all = "lowercase"))] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub enum ConnectionStatusKind { + #[default] + Disconnected, + Disconnecting, + Connected, + Connecting, +} + +#[cfg_attr(test, derive(ts_rs::TS))] +#[cfg_attr(test, ts(rename_all = "lowercase"))] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] +#[serde(rename_all = "camelCase")] +pub enum GatewayConnectionStatusKind { + Good, + Bad, + VeryBad, +} + +impl From for GatewayConnectionStatusKind { + fn from(conn: GatewayConnectivity) -> Self { + match conn { + GatewayConnectivity::Good => GatewayConnectionStatusKind::Good, + GatewayConnectivity::Bad { .. } => GatewayConnectionStatusKind::Bad, + GatewayConnectivity::VeryBad { .. } => GatewayConnectionStatusKind::VeryBad, + } + } +} + +#[cfg_attr(test, derive(ts_rs::TS))] +#[cfg_attr(test, ts(rename_all = "lowercase"))] +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Default)] +pub enum ConnectivityTestResult { + #[default] + NotAvailable, + Success, + Fail, +} + +impl fmt::Display for ConnectionStatusKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + ConnectionStatusKind::Disconnected => write!(f, "Disconnected"), + ConnectionStatusKind::Disconnecting => write!(f, "Disconnecting"), + ConnectionStatusKind::Connected => write!(f, "Connected"), + ConnectionStatusKind::Connecting => write!(f, "Connecting"), + } + } +} + +pub const APP_EVENT_CONNECTION_STATUS_CHANGED: &str = "app:connection-status-changed"; + +#[cfg_attr(test, derive(ts_rs::TS))] +#[derive(Clone, Serialize)] +pub struct AppEventConnectionStatusChangedPayload { + pub status: ConnectionStatusKind, +} + +#[cfg_attr(test, derive(ts_rs::TS))] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DirectoryService { + pub id: String, + pub description: String, + pub items: Vec, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct HarbourMasterService { + pub service_provider_client_id: String, + pub gateway_identity_key: String, + pub ip_address: String, + pub last_successful_ping_utc: String, + pub last_updated_utc: String, + pub routing_score: f32, +} + +#[cfg_attr(test, derive(ts_rs::TS))] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DirectoryServiceProvider { + pub id: String, + pub description: String, + /// Address of the network requester in the form "." + /// e.g. DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh + pub address: String, + /// Address of the gateway, e.g. 2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh + pub gateway: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PagedResult { + pub page: u32, + pub size: u32, + pub total: i32, + pub items: Vec, +} diff --git a/nym-connect/mobile/src-tauri/src/operations/connection/connect.rs b/nym-connect/mobile/src-tauri/src/operations/connection/connect.rs new file mode 100644 index 0000000000..1d83748698 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/connection/connect.rs @@ -0,0 +1,68 @@ +use crate::{ + error::{BackendError, Result}, + models::ConnectResult, + tasks, State, +}; +use std::sync::Arc; +use tokio::sync::RwLock; + +#[tauri::command] +pub async fn start_connecting( + state: tauri::State<'_, Arc>>, + window: tauri::Window, +) -> Result { + log::trace!("Start connecting"); + let (msg_receiver, exit_status_receiver) = { + let mut state_w = state.write().await; + state_w.start_connecting(&window).await? + }; + + // Setup task for checking status + let state = state.inner().clone(); + tasks::start_disconnect_listener(state.clone(), window.clone(), exit_status_receiver); + tasks::start_status_listener(state, window.clone(), msg_receiver); + + Ok(ConnectResult { + address: "PLACEHOLDER".to_string(), + }) +} + +#[tauri::command] +pub async fn get_service_provider(state: tauri::State<'_, Arc>>) -> Result { + let guard = state.read().await; + guard + .get_service_provider() + .clone() + .ok_or(BackendError::NoServiceProviderSet) +} + +#[tauri::command] +pub async fn set_service_provider( + service_provider: Option, + state: tauri::State<'_, Arc>>, +) -> Result<()> { + log::trace!("Setting service_provider: {:?}", &service_provider); + let mut guard = state.write().await; + guard.set_service_provider(service_provider); + Ok(()) +} + +#[tauri::command] +pub async fn get_gateway(state: tauri::State<'_, Arc>>) -> Result { + let guard = state.read().await; + guard + .get_gateway() + .clone() + .ok_or(BackendError::NoGatewaySet) +} + +#[tauri::command] +pub async fn set_gateway( + gateway: Option, + state: tauri::State<'_, Arc>>, +) -> Result<()> { + log::trace!("Setting gateway: {:?}", &gateway); + let mut guard = state.write().await; + guard.set_gateway(gateway); + Ok(()) +} diff --git a/nym-connect/mobile/src-tauri/src/operations/connection/disconnect.rs b/nym-connect/mobile/src-tauri/src/operations/connection/disconnect.rs new file mode 100644 index 0000000000..b75bf49410 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/connection/disconnect.rs @@ -0,0 +1,20 @@ +use crate::error::Result; +use crate::models::ConnectResult; +use crate::State; +use std::sync::Arc; +use tokio::sync::RwLock; + +#[tauri::command] +pub async fn start_disconnecting( + state: tauri::State<'_, Arc>>, + window: tauri::Window, +) -> Result { + log::trace!("Start disconnecting"); + let mut guard = state.write().await; + + guard.start_disconnecting(&window).await?; + + Ok(ConnectResult { + address: "PLACEHOLDER".to_string(), + }) +} diff --git a/nym-connect/mobile/src-tauri/src/operations/connection/health_check.rs b/nym-connect/mobile/src-tauri/src/operations/connection/health_check.rs new file mode 100644 index 0000000000..3f401933a1 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/connection/health_check.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +static HEALTH_CHECK_URL: &str = "https://nymtech.net/.wellknown/connect/healthcheck.json"; + +#[derive(Serialize, Deserialize, Debug)] +struct ConnectionSuccess { + status: String, +} + +pub async fn run_health_check() -> bool { + log::info!("Running network health check"); + match crate::operations::http::socks5_get::<_, ConnectionSuccess>(HEALTH_CHECK_URL).await { + Ok(res) if res.status == "ok" => { + log::info!("Healthcheck success!"); + true + } + Ok(res) => { + log::error!("Healthcheck failed with status: {}", res.status); + false + } + Err(err) => { + log::error!("Healthcheck failed: {err}"); + false + } + } +} diff --git a/nym-connect/mobile/src-tauri/src/operations/connection/mod.rs b/nym-connect/mobile/src-tauri/src/operations/connection/mod.rs new file mode 100644 index 0000000000..171d07cd83 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/connection/mod.rs @@ -0,0 +1,4 @@ +pub mod connect; +pub mod disconnect; +pub mod health_check; +pub mod status; diff --git a/nym-connect/mobile/src-tauri/src/operations/connection/status.rs b/nym-connect/mobile/src-tauri/src/operations/connection/status.rs new file mode 100644 index 0000000000..7710524fb5 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/connection/status.rs @@ -0,0 +1,44 @@ +use crate::error::Result; +use crate::tasks; +use std::sync::Arc; + +use tokio::sync::RwLock; + +use crate::models::{ConnectionStatusKind, ConnectivityTestResult, GatewayConnectionStatusKind}; +use crate::state::State; + +#[tauri::command] +pub async fn get_connection_status( + state: tauri::State<'_, Arc>>, +) -> Result { + let state = state.read().await; + Ok(state.get_status()) +} + +#[tauri::command] +pub async fn get_gateway_connection_status( + state: tauri::State<'_, Arc>>, +) -> Result { + let mut state_w = state.write().await; + let gateway_connectivity = state_w.get_gateway_connectivity(); + Ok(gateway_connectivity.into()) +} + +#[tauri::command] +pub async fn get_connection_health_check_status( + state: tauri::State<'_, Arc>>, +) -> Result { + let state = state.read().await; + Ok(state.get_connectivity_test_result()) +} + +// Start a connection check task. This should return with an event within one minute, and update +// the state. +// Trying to run multiple concurrent connection checks probably works but is not supported. +#[tauri::command] +pub fn start_connection_health_check_task( + state: tauri::State<'_, Arc>>, + window: tauri::Window, +) { + tasks::start_connection_check(state.inner().clone(), window); +} diff --git a/nym-connect/mobile/src-tauri/src/operations/directory/mod.rs b/nym-connect/mobile/src-tauri/src/operations/directory/mod.rs new file mode 100644 index 0000000000..0e58035274 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/directory/mod.rs @@ -0,0 +1,92 @@ +use itertools::Itertools; + +use crate::error::Result; +use crate::models::{ + DirectoryService, DirectoryServiceProvider, HarbourMasterService, PagedResult, +}; +use nym_api_requests::models::GatewayBondAnnotated; +use nym_contracts_common::types::Percent; + +static SERVICE_PROVIDER_WELLKNOWN_URL: &str = + "https://nymtech.net/.wellknown/connect/service-providers.json"; + +static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100"; + +static GATEWAYS_DETAILED_URL: &str = + "https://validator.nymtech.net/api/v1/status/gateways/detailed"; + +#[tauri::command] +pub async fn get_services() -> Result> { + log::trace!("Fetching services"); + let services_res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL) + .await? + .json::>() + .await?; + log::trace!("Received: {:#?}", services_res); + + log::trace!("Fetching gateways"); + let gateway_res = reqwest::get(GATEWAYS_DETAILED_URL) + .await? + .json::>() + .await?; + log::trace!("Received: {:#?}", gateway_res); + + // TODO: get paged + log::trace!("Fetching active services"); + let active_services = reqwest::get(HARBOUR_MASTER_URL) + .await? + .json::>() + .await?; + log::trace!("Active: {:#?}", active_services); + + let mut filtered: Vec = vec![]; + + for service in &services_res { + let items: _ = service + .items + .clone() + .into_iter() + .filter(|sp| { + active_services + .items + .iter() + .any(|active| active.service_provider_client_id == sp.address) + }) + .collect_vec(); + log::trace!("service = {} has {} items", service.id, items.len()); + filtered.push(DirectoryService { + id: service.id.clone(), + description: service.description.clone(), + items, + }) + } + + let perf_threshold = Percent::from_percentage_value(90).unwrap(); + + // Use only services that are active AND have a performance of >= 90% + let services_with_good_performance: Vec = filtered + .iter_mut() + .fold(vec![], |mut acc, sp| { + acc.append(&mut sp.items); + acc + }) + .into_iter() + .filter(|sp| { + gateway_res.iter().any(|gateway| { + gateway.gateway_bond.gateway.identity_key == sp.gateway + && gateway.performance >= perf_threshold + }) + }) + .collect(); + + Ok(services_with_good_performance) +} + +#[tauri::command] +pub async fn get_gateways_detailed() -> Result> { + let res = reqwest::get(GATEWAYS_DETAILED_URL) + .await? + .json::>() + .await?; + Ok(res) +} diff --git a/nym-connect/mobile/src-tauri/src/operations/export.rs b/nym-connect/mobile/src-tauri/src/operations/export.rs new file mode 100644 index 0000000000..1ebff1a35a --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/export.rs @@ -0,0 +1,91 @@ +use std::{ffi::OsStr, fs, sync::Arc}; +use tokio::sync::RwLock; + +use crate::{ + error::{BackendError, Result}, + state::State, +}; +use client_core::client::key_manager::KeyManager; +use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; +use nym_crypto::asymmetric::identity; + +pub async fn get_identity_key( + state: &tauri::State<'_, Arc>>, +) -> Result> { + let config = { + let state = state.read().await; + state.load_socks5_config()? + }; + + let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); + let key_manager = KeyManager::load_keys(&pathfinder)?; + let identity_keypair = key_manager.identity_keypair(); + + Ok(identity_keypair) +} + +/// Export the gateway keys as a JSON string blob +#[tauri::command] +pub async fn export_keys(state: tauri::State<'_, Arc>>) -> Result { + let config = { + let state = state.read().await; + state.load_socks5_config()? + }; + + // Get key paths + let ack_key_file = config.get_base().get_ack_key_file(); + let gateway_shared_key_file = config.get_base().get_gateway_shared_key_file(); + + let pub_id_key_file = config.get_base().get_public_identity_key_file(); + let priv_id_key_file = config.get_base().get_private_identity_key_file(); + + let pub_enc_key_file = config.get_base().get_public_encryption_key_file(); + let priv_enc_key_file = config.get_base().get_private_encryption_key_file(); + + // Read file contents + let ack_key = fs::read_to_string(ack_key_file.clone())?; + let gateway_shared_key = fs::read_to_string(gateway_shared_key_file.clone())?; + + let pub_id_key = fs::read_to_string(pub_id_key_file.clone())?; + let priv_id_key = fs::read_to_string(priv_id_key_file.clone())?; + + let pub_enc_key = fs::read_to_string(pub_enc_key_file.clone())?; + let priv_enc_key = fs::read_to_string(priv_enc_key_file.clone())?; + + let ack_key_file = ack_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + let gateway_shared_key_file = gateway_shared_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + let pub_id_key_file = pub_id_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + let priv_id_key_file = priv_id_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + let pub_enc_key_file = pub_enc_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + let priv_enc_key_file = priv_enc_key_file + .file_name() + .map(OsStr::to_string_lossy) + .ok_or(BackendError::CouldNotGetFilename)?; + + // Format and return as json + let json = serde_json::json!({ + ack_key_file: ack_key, + gateway_shared_key_file: gateway_shared_key, + pub_id_key_file: pub_id_key, + priv_id_key_file: priv_id_key, + pub_enc_key_file: pub_enc_key, + priv_enc_key_file: priv_enc_key, + }); + + Ok(serde_json::to_string_pretty(&json)?) +} diff --git a/nym-connect/mobile/src-tauri/src/operations/help/log.rs b/nym-connect/mobile/src-tauri/src/operations/help/log.rs new file mode 100644 index 0000000000..5b31ea023e --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/help/log.rs @@ -0,0 +1,31 @@ +use crate::error::BackendError; +use tauri::Manager; + +#[cfg(desktop)] +#[tauri::command] +pub fn help_log_toggle_window(app_handle: tauri::AppHandle) -> Result<(), BackendError> { + if let Some(current_log_window) = app_handle.windows().get("log") { + log::info!("Closing log window..."); + if let Err(e) = current_log_window.close() { + log::error!("Unable to close log window: {:?}", e); + } + return Ok(()); + } + + log::info!("Creating log window..."); + match tauri::WindowBuilder::new(&app_handle, "log", tauri::WindowUrl::App("log.html".into())) + .title("Nym Connect Logs") + .build() + { + Ok(window) => { + if let Err(e) = window.set_focus() { + log::error!("Unable to focus log window: {:?}", e); + } + Ok(()) + } + Err(e) => { + log::error!("Unable to create log window: {:?}", e); + Err(BackendError::NewWindowError) + } + } +} diff --git a/nym-connect/mobile/src-tauri/src/operations/help/mod.rs b/nym-connect/mobile/src-tauri/src/operations/help/mod.rs new file mode 100644 index 0000000000..52dafe6d0f --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/help/mod.rs @@ -0,0 +1,2 @@ +pub mod log; +pub mod storage; diff --git a/nym-connect/mobile/src-tauri/src/operations/help/storage.rs b/nym-connect/mobile/src-tauri/src/operations/help/storage.rs new file mode 100644 index 0000000000..ab47ffa529 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/help/storage.rs @@ -0,0 +1,20 @@ +use crate::error::BackendError; +use serde::Serialize; +use tauri::Manager; + +#[derive(Debug, Serialize, Clone)] +struct ClearStorageEvent { + kind: String, +} + +#[tauri::command] +pub fn help_clear_storage(app_handle: tauri::AppHandle) -> Result<(), BackendError> { + log::info!("Sending event to clear local storage..."); + + let event = ClearStorageEvent { + kind: "local_storage".to_string(), + }; + app_handle.emit_all("help://clear-storage", event)?; + + Ok(()) +} diff --git a/nym-connect/mobile/src-tauri/src/operations/http.rs b/nym-connect/mobile/src-tauri/src/operations/http.rs new file mode 100644 index 0000000000..5cfe3e1f53 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/http.rs @@ -0,0 +1,37 @@ +use crate::error::{BackendError, Result}; +use serde::de::DeserializeOwned; +use tap::TapFallible; + +pub async fn socks5_get(url: U) -> Result +where + U: reqwest::IntoUrl + std::fmt::Display, + T: DeserializeOwned, +{ + log::info!(">>> GET {url}"); + let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?; + let client = reqwest::Client::builder() + .proxy(proxy) + .timeout(std::time::Duration::from_secs(20)) + .build()?; + + let resp = client.get(url).send().await.tap_err(|err| { + log::error!("<<< Request send error: {err}"); + })?; + + if resp.status().is_client_error() || resp.status().is_server_error() { + log::error!("<<< {}", resp.status()); + return Err(BackendError::RequestFail { + url: resp.url().clone(), + status_code: resp.status(), + }); + } + + let response_body = resp.text().await.tap_err(|err| { + log::error!("<<< Request error: {err}"); + })?; + log::info!("<<< {response_body}"); + + Ok(serde_json::from_str(&response_body).tap_err(|err| { + log::error!("<<< JSON parsing error: {err}"); + })?) +} diff --git a/nym-connect/mobile/src-tauri/src/operations/mod.rs b/nym-connect/mobile/src-tauri/src/operations/mod.rs new file mode 100644 index 0000000000..e2aa1aa925 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/mod.rs @@ -0,0 +1,7 @@ +pub mod connection; +pub mod directory; +pub mod export; +pub mod help; +pub mod http; +#[cfg(desktop)] +pub mod window; diff --git a/nym-connect/mobile/src-tauri/src/operations/window/mod.rs b/nym-connect/mobile/src-tauri/src/operations/window/mod.rs new file mode 100644 index 0000000000..b89cdb88ff --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/operations/window/mod.rs @@ -0,0 +1,7 @@ +use crate::window::window_hide; +use tauri::{AppHandle, Wry}; + +#[tauri::command] +pub fn hide_window(app: AppHandle) { + window_hide(&app); +} diff --git a/nym-connect/mobile/src-tauri/src/state.rs b/nym-connect/mobile/src-tauri/src/state.rs new file mode 100644 index 0000000000..730665f0dd --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/state.rs @@ -0,0 +1,262 @@ +use std::time::Duration; + +use ::nym_config_common::NymConfig; +use client_core::client::key_manager::KeyManager; +use client_core::error::ClientCoreStatusMessage; +use futures::SinkExt; +use tap::TapFallible; +use tauri::Manager; + +use nym_socks5::client::{ + config::Config as Socks5Config, Socks5ControlMessage, Socks5ControlMessageSender, +}; +use tokio::time::Instant; + +use crate::{ + config::{self, socks5_config_id_appended_with, Config}, + error::{BackendError, Result}, + models::{ + AppEventConnectionStatusChangedPayload, ConnectionStatusKind, ConnectivityTestResult, + APP_EVENT_CONNECTION_STATUS_CHANGED, + }, + tasks::{self, ExitStatusReceiver}, +}; + +// The client will emit messages if the connection to the gateway is poor (or the gateway can't +// keep up with the messages we are sendind). If no messages about this has been received for a +// certain duration then we assume it's all good. +const GATEWAY_CONNECTIVITY_TIMEOUT_SECS: u64 = 20; + +#[derive(Clone, Copy, Default)] +pub enum GatewayConnectivity { + #[default] + Good, + Bad { + when: Instant, + }, + VeryBad { + when: Instant, + }, +} + +impl TryFrom<&ClientCoreStatusMessage> for GatewayConnectivity { + type Error = BackendError; + + fn try_from(value: &ClientCoreStatusMessage) -> Result { + let conn = match value { + ClientCoreStatusMessage::GatewayIsSlow => GatewayConnectivity::Bad { + when: Instant::now(), + }, + ClientCoreStatusMessage::GatewayIsVerySlow => GatewayConnectivity::VeryBad { + when: Instant::now(), + }, + }; + Ok(conn) + } +} + +#[derive(Default)] +pub struct State { + /// The current connection status + status: ConnectionStatusKind, + + /// The service provider + service_provider: Option, + + /// The gateway used. Note that this is also used to create the configuration id + gateway: Option, + + /// Channel that is used to send command messages to the SOCKS5 client, such as to disconnect + socks5_client_sender: Option, + + /// The client will periodically report connectivity to the gateway it's connected to. Unless + /// we get a status message from the client we assume it's good. + gateway_connectivity: GatewayConnectivity, + + /// The latest end-to-end connectivity test result. The first test is initiated on connection + /// established. Additional tests can be triggered. + connectivity_test_result: ConnectivityTestResult, +} + +impl State { + pub fn new() -> Self { + State { + status: ConnectionStatusKind::Disconnected, + service_provider: None, + gateway: None, + socks5_client_sender: None, + gateway_connectivity: GatewayConnectivity::Good, + connectivity_test_result: ConnectivityTestResult::NotAvailable, + } + } + + pub fn get_gateway_connectivity(&mut self) -> GatewayConnectivity { + self.gateway_connectivity = match self.gateway_connectivity { + c @ (GatewayConnectivity::Bad { when } | GatewayConnectivity::VeryBad { when }) => { + if Instant::now() > when + Duration::from_secs(GATEWAY_CONNECTIVITY_TIMEOUT_SECS) { + GatewayConnectivity::Good + } else { + c + } + } + current => current, + }; + self.gateway_connectivity + } + + pub fn set_gateway_connectivity(&mut self, gateway_connectivity: GatewayConnectivity) { + self.gateway_connectivity = gateway_connectivity + } + + pub fn get_connectivity_test_result(&self) -> ConnectivityTestResult { + self.connectivity_test_result + } + + pub fn set_connectivity_test_result( + &mut self, + connectivity_test_result: ConnectivityTestResult, + ) { + self.connectivity_test_result = connectivity_test_result; + } + + pub fn get_status(&self) -> ConnectionStatusKind { + self.status.clone() + } + + fn set_state(&mut self, status: ConnectionStatusKind, window: &tauri::Window) { + log::info!("{status}"); + self.status = status.clone(); + window + .emit_all( + APP_EVENT_CONNECTION_STATUS_CHANGED, + AppEventConnectionStatusChangedPayload { status }, + ) + .tap_err(|err| log::warn!("{err}")) + .ok(); + } + + pub fn get_service_provider(&self) -> &Option { + &self.service_provider + } + + pub fn set_service_provider(&mut self, provider: Option) { + self.service_provider = provider; + } + + pub fn get_gateway(&self) -> &Option { + &self.gateway + } + + pub fn set_gateway(&mut self, gateway: Option) { + self.gateway = gateway; + } + + /// The effective config id is the static config id appended with the id of the gateway + pub fn get_config_id(&self) -> Result { + self.get_gateway() + .as_ref() + .ok_or(BackendError::CouldNotGetIdWithoutGateway) + .and_then(|gateway_id| socks5_config_id_appended_with(gateway_id)) + } + + pub fn load_socks5_config(&self) -> Result { + let id = self.get_config_id()?; + let config = Socks5Config::load_from_file(&id) + .tap_err(|_| log::warn!("Failed to load configuration file"))?; + Ok(config) + } + + /// Start connecting by first creating a config file, followed by starting a thread running the + /// SOCKS5 client. + pub async fn start_connecting( + &mut self, + window: &tauri::Window, + ) -> Result<(nym_task::StatusReceiver, ExitStatusReceiver)> { + self.set_state(ConnectionStatusKind::Connecting, window); + + let res = self.init_config().await; + match res { + Ok(_) => {} + Err(e) => { + log::error!("Failed to initialize: {e}"); + + // Wait a little to give the user some rudimentary feedback that the click actually + // registered. + tokio::time::sleep(Duration::from_secs(1)).await; + self.set_state(ConnectionStatusKind::Disconnected, window); + return Err(e); + } + }; + let (config, keys) = res.unwrap(); + + // Kick off the main task and get the channel for controlling it + self.start_nym_socks5_client(config, keys) + } + + /// Create a configuration file + async fn init_config(&self) -> Result<(Config, KeyManager)> { + let service_provider = self + .get_service_provider() + .as_ref() + .ok_or(BackendError::CouldNotInitWithoutServiceProvider)?; + let gateway = self + .get_gateway() + .as_ref() + .ok_or(BackendError::CouldNotInitWithoutGateway)?; + log::trace!(" service_provider: {:?}", service_provider); + log::trace!(" gateway: {:?}", gateway); + + config::Config::init(service_provider, gateway).await + } + + /// Spawn a new thread running the SOCKS5 client + fn start_nym_socks5_client( + &mut self, + config: Config, + keys: KeyManager, + ) -> Result<(nym_task::StatusReceiver, ExitStatusReceiver)> { + let id = self.get_config_id()?; + let (control_tx, msg_rx, exit_status_rx, used_gateway) = + tasks::start_nym_socks5_client(&id, config, keys)?; + self.socks5_client_sender = Some(control_tx); + self.gateway = Some(used_gateway.gateway_id); + Ok((msg_rx, exit_status_rx)) + } + + /// Once the SOCKS5 client is operational, the status listener would call this + pub fn mark_connected(&mut self, window: &tauri::Window) { + log::trace!("state::mark_connected"); + self.set_state(ConnectionStatusKind::Connected, window); + } + + /// Disconnect by sending a message to the SOCKS5 client thread. Once it has finished and is + /// disconnected, the disconnect handler will mark it as disconnected. + pub async fn start_disconnecting(&mut self, window: &tauri::Window) -> Result<()> { + log::trace!("state::start_disconnecting"); + self.set_state(ConnectionStatusKind::Disconnecting, window); + + // Send shutdown message + match self.socks5_client_sender { + Some(ref mut sender) => sender + .send(Socks5ControlMessage::Stop) + .await + .map_err(|err| { + log::warn!("Failed trying to send disconnect signal: {err}"); + BackendError::CoundNotSendDisconnectSignal + }), + None => { + log::warn!( + "Trying to disconnect without being able to talk to the SOCKS5 client, \ + is it running?" + ); + Err(BackendError::CoundNotSendDisconnectSignal) + } + } + } + + /// Once the SOCKS5 client has stopped, this should be called by the disconnect handler to mark + /// the state as disconnected. + pub fn mark_disconnected(&mut self, window: &tauri::Window) { + self.set_state(ConnectionStatusKind::Disconnected, window); + } +} diff --git a/nym-connect/mobile/src-tauri/src/tasks.rs b/nym-connect/mobile/src-tauri/src/tasks.rs new file mode 100644 index 0000000000..801139e683 --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/tasks.rs @@ -0,0 +1,205 @@ +use client_core::{ + client::key_manager::KeyManager, + config::{ClientCoreConfigTrait, GatewayEndpointConfig}, + error::ClientCoreStatusMessage, +}; +use futures::{channel::mpsc, StreamExt}; +use nym_task::manager::TaskStatus; +use std::sync::Arc; +use tap::TapFallible; +use tokio::sync::RwLock; + +use nym_config_common::NymConfig; +use nym_socks5::client::NymClient as Socks5NymClient; +use nym_socks5::client::{config::Config as Socks5Config, Socks5ControlMessageSender}; + +use crate::{ + config::Config, + error::Result, + events::{self, emit_event, emit_status_event}, + models::{ConnectionStatusKind, ConnectivityTestResult}, + operations, + state::State, +}; + +pub type ExitStatusReceiver = futures::channel::oneshot::Receiver; + +/// Status messages sent by the SOCKS5 client task to the main tauri task. +#[derive(Debug)] +pub enum Socks5ExitStatusMessage { + /// The SOCKS5 task successfully stopped + Stopped, + /// The SOCKS5 task failed to start + Failed(Box), +} + +/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`. +pub fn start_nym_socks5_client( + id: &str, + config: Config, + keys: KeyManager, +) -> Result<( + Socks5ControlMessageSender, + nym_task::StatusReceiver, + ExitStatusReceiver, + GatewayEndpointConfig, +)> { + log::info!("Loading config from file: {id}"); + let used_gateway = config.get_base().get_gateway_endpoint().clone(); + + let socks5_client = Socks5NymClient::new_with_keys(config.socks5, Some(keys)); + log::info!("Starting socks5 client"); + + // Channel to send control messages to the socks5 client + let (socks5_ctrl_tx, socks5_ctrl_rx) = mpsc::unbounded(); + + // Channel to send status update messages from the background socks5 task to the frontend. + let (socks5_status_tx, socks5_status_rx) = mpsc::channel(128); + + // Channel to signal back to the main task when the socks5 client finishes, and why + let (socks5_exit_tx, socks5_exit_rx) = futures::channel::oneshot::channel(); + + // Spawn a separate runtime for the socks5 client so we can forcefully terminate. + // Once we can gracefully shutdown the socks5 client we can get rid of this. + // The status channel is used to both get the state of the task, and if it's closed, to check + // for panic. + std::thread::spawn(|| { + let result = tokio::runtime::Runtime::new() + .expect("Failed to create runtime for SOCKS5 client") + .block_on(async move { + socks5_client + .run_and_listen(socks5_ctrl_rx, socks5_status_tx) + .await + }); + + if let Err(err) = result { + log::error!("SOCKS5 proxy failed: {err}"); + socks5_exit_tx + .send(Socks5ExitStatusMessage::Failed(err)) + .expect("Failed to send status message back to main task"); + return; + } + + log::info!("SOCKS5 task finished"); + socks5_exit_tx + .send(Socks5ExitStatusMessage::Stopped) + .expect("Failed to send status message back to main task"); + }); + + Ok(( + socks5_ctrl_tx, + socks5_status_rx, + socks5_exit_rx, + used_gateway, + )) +} + +pub fn start_connection_check(state: Arc>, window: tauri::Window) { + log::debug!("Starting connection check handler"); + tokio::spawn(async move { + if state.read().await.get_status() != ConnectionStatusKind::Connected { + log::error!("SOCKS5 connection status check failed: not connected"); + return; + } + + log::info!("Running connection health check"); + if operations::connection::health_check::run_health_check().await { + state + .write() + .await + .set_connectivity_test_result(ConnectivityTestResult::Success); + emit_event( + "socks5-connection-success-event", + "SOCKS5 success", + "SOCKS5 connection health check successful", + &window, + ); + } else if state.read().await.get_status() == ConnectionStatusKind::Connected { + state + .write() + .await + .set_connectivity_test_result(ConnectivityTestResult::Fail); + log::error!("SOCKS5 connection health check failed"); + emit_event( + "socks5-connection-fail-event", + "SOCKS5 error", + "SOCKS5 connection health check failed", + &window, + ); + } else { + log::debug!("SOCKS5 connection status check cancelled: not connected"); + } + + log::debug!("Connection check handler exiting"); + }); +} + +/// The status listener listens for non-exit status messages from the background socks5 proxy task. +pub fn start_status_listener( + state: Arc>, + window: tauri::Window, + mut msg_receiver: nym_task::StatusReceiver, +) { + log::info!("Starting status listener"); + + tokio::spawn(async move { + while let Some(msg) = msg_receiver.next().await { + log::info!("SOCKS5 proxy sent status message: {}", msg); + + if let Some(task_status) = msg.downcast_ref::() { + events::handle_task_status(task_status, &state, &window).await; + } else if let Some(client_status_message) = + msg.downcast_ref::() + { + events::handle_client_status_message(client_status_message, &state, &window).await; + } else { + emit_status_event("socks5-status-event", &msg, &window); + } + } + log::info!("Status listener exiting"); + }); +} + +/// The disconnect listener listens to the channel setup between the socks5 proxy task and the main +/// tauri task. Primarily it listens for shutdown messages, and updates the state accordingly. +pub fn start_disconnect_listener( + state: Arc>, + window: tauri::Window, + exit_status_receiver: ExitStatusReceiver, +) { + log::trace!("Starting disconnect listener"); + tokio::spawn(async move { + match exit_status_receiver.await { + Ok(Socks5ExitStatusMessage::Stopped) => { + log::info!("SOCKS5 task reported it has finished"); + emit_event( + "socks5-event", + "SOCKS5 finished", + "SOCKS5 task reported it has finished", + &window, + ); + } + Ok(Socks5ExitStatusMessage::Failed(err)) => { + log::info!("SOCKS5 task reported error: {err}"); + emit_event( + "socks5-event", + "SOCKS5 error", + &format!("SOCKS5 failed: {err}"), + &window, + ); + } + Err(_) => { + log::info!("SOCKS5 task appears to have stopped abruptly"); + emit_event( + "socks5-event", + "SOCKS5 error", + "SOCKS5 stopped abruptly. Please try reconnecting.", + &window, + ); + } + } + + let mut state_w = state.write().await; + state_w.mark_disconnected(&window); + }); +} diff --git a/nym-connect/mobile/src-tauri/src/window.rs b/nym-connect/mobile/src-tauri/src/window.rs new file mode 100644 index 0000000000..84cb3e41cd --- /dev/null +++ b/nym-connect/mobile/src-tauri/src/window.rs @@ -0,0 +1,30 @@ +use crate::menu::TRAY_MENU_SHOW_HIDE; +use tauri::{AppHandle, Manager}; + +pub(crate) fn window_hide(app: &AppHandle) { + let window = app.get_window("main").unwrap(); + let item_handle = app.tray_handle().get_item(TRAY_MENU_SHOW_HIDE); + if window.is_visible().unwrap() { + window.hide().unwrap(); + item_handle.set_title("Show").unwrap(); + } +} + +pub(crate) fn window_show(app: &AppHandle) { + let window = app.get_window("main").unwrap(); + let item_handle = app.tray_handle().get_item(TRAY_MENU_SHOW_HIDE); + if !window.is_visible().unwrap() { + window.show().unwrap(); + item_handle.set_title("Hide").unwrap(); + window.set_focus().unwrap(); + } +} + +pub(crate) fn window_toggle(app: &AppHandle) { + let window = app.get_window("main").unwrap(); + if window.is_visible().unwrap() { + window_hide(app); + } else { + window_show(app); + } +} diff --git a/nym-connect/mobile/src-tauri/tauri.conf.json b/nym-connect/mobile/src-tauri/tauri.conf.json new file mode 100644 index 0000000000..a45666f724 --- /dev/null +++ b/nym-connect/mobile/src-tauri/tauri.conf.json @@ -0,0 +1,77 @@ +{ + "package": { + "productName": "nym-connect", + "version": "1.1.9" + }, + "build": { + "distDir": "../dist", + "devPath": "http://localhost:9000", + "beforeDevCommand": "", + "beforeBuildCommand": "" + }, + "tauri": { + "bundle": { + "active": true, + "targets": "all", + "identifier": "net.nymtech.connect", + "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], + "resources": [], + "externalBin": [], + "copyright": "Copyright © 2021-2022 Nym Technologies SA", + "category": "Business", + "shortDescription": "Browse the internet privately using the Nym Mixnet", + "longDescription": "", + "deb": { + "depends": [] + }, + "macOS": { + "frameworks": [], + "minimumSystemVersion": "", + "exceptionDomain": "", + "signingIdentity": null, + "entitlements": null + }, + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + } + }, + "systemTray": { + "iconPath": "icons/tray_icon.png", + "iconAsTemplate": true + }, + "updater": { + "active": false + }, + "allowlist": { + "shell": { + "open": true + }, + "clipboard": { + "writeText": true + }, + "window": { + "startDragging": true, + "close": true, + "minimize": true + }, + "notification": { + "all": true + } + }, + "windows": [ + { + "title": "NymConnect", + "width": 240, + "height": 635, + "resizable": false, + "decorations": false, + "transparent": true + } + ], + "security": { + "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" + } + } +} diff --git a/nym-connect/mobile/src/assets/help-step-four.png b/nym-connect/mobile/src/assets/help-step-four.png new file mode 100644 index 0000000000..5507962892 Binary files /dev/null and b/nym-connect/mobile/src/assets/help-step-four.png differ diff --git a/nym-connect/mobile/src/assets/help-step-one-legacy.png b/nym-connect/mobile/src/assets/help-step-one-legacy.png new file mode 100644 index 0000000000..5e91a29cff Binary files /dev/null and b/nym-connect/mobile/src/assets/help-step-one-legacy.png differ diff --git a/nym-connect/mobile/src/assets/help-step-one.png b/nym-connect/mobile/src/assets/help-step-one.png new file mode 100644 index 0000000000..482e752286 Binary files /dev/null and b/nym-connect/mobile/src/assets/help-step-one.png differ diff --git a/nym-connect/mobile/src/assets/help-step-three.png b/nym-connect/mobile/src/assets/help-step-three.png new file mode 100644 index 0000000000..932b6c1171 Binary files /dev/null and b/nym-connect/mobile/src/assets/help-step-three.png differ diff --git a/nym-connect/mobile/src/assets/help-step-two-legacy.png b/nym-connect/mobile/src/assets/help-step-two-legacy.png new file mode 100644 index 0000000000..f2910c98cf Binary files /dev/null and b/nym-connect/mobile/src/assets/help-step-two-legacy.png differ diff --git a/nym-connect/mobile/src/assets/help-step-two.png b/nym-connect/mobile/src/assets/help-step-two.png new file mode 100644 index 0000000000..c6f4429d74 Binary files /dev/null and b/nym-connect/mobile/src/assets/help-step-two.png differ diff --git a/nym-connect/mobile/src/components/AppVersion.tsx b/nym-connect/mobile/src/components/AppVersion.tsx new file mode 100644 index 0000000000..4ced230efa --- /dev/null +++ b/nym-connect/mobile/src/components/AppVersion.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import { useClientContext } from 'src/context/main'; + +export const AppVersion = () => { + const { appVersion } = useClientContext(); + + return ( + + + Version {appVersion} + + + ); +}; diff --git a/nym-connect/mobile/src/components/AppWindowFrame.tsx b/nym-connect/mobile/src/components/AppWindowFrame.tsx new file mode 100644 index 0000000000..88a423d15e --- /dev/null +++ b/nym-connect/mobile/src/components/AppWindowFrame.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import { useLocation } from 'react-router-dom'; +import { useClientContext } from 'src/context/main'; +import { CustomTitleBar } from './CustomTitleBar'; + +export const AppWindowFrame: FCWithChildren = ({ children }) => { + const location = useLocation(); + const { userDefinedGateway, setUserDefinedGateway } = useClientContext(); + + // defined functions to be used when moving away from pages + const onBack = () => { + switch (location.pathname) { + case '/menu/settings': + return () => { + // when the user moves away from the settings page and the gateway is not valid + // set isActive to false + if (!userDefinedGateway?.gateway) { + setUserDefinedGateway((current) => ({ ...current, isActive: false })); + } + }; + default: + return undefined; + } + }; + + return ( + + + {children} + + ); +}; diff --git a/nym-connect/mobile/src/components/ConnectionButton.tsx b/nym-connect/mobile/src/components/ConnectionButton.tsx new file mode 100644 index 0000000000..1dd72f78df --- /dev/null +++ b/nym-connect/mobile/src/components/ConnectionButton.tsx @@ -0,0 +1,168 @@ +import React from 'react'; +import { ConnectionStatusKind } from '../types'; + +const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => { + if (isError && hover) { + return '#21D072'; + } + if (isError) { + return '#40475C'; + } + + switch (status) { + case 'disconnected': + if (hover) { + return '#FFFF33'; + } + return '#FFE600'; + case 'connecting': + case 'disconnecting': + return '#FFE600'; + default: + // connected + if (hover) { + return '#E43E3E'; + } + return '#21D072'; + } +}; + +const getStatusText = (status: ConnectionStatusKind, hover: boolean): string => { + switch (status) { + case 'disconnected': + return 'Connect'; + case 'connecting': + return 'Connecting'; + case 'disconnecting': + return 'Connected'; + default: + // connected + if (hover) { + return 'Disconnect'; + } + return 'Connected'; + } +}; + +export const ConnectionButton: FCWithChildren<{ + status: ConnectionStatusKind; + disabled?: boolean; + busy?: boolean; + isError?: boolean; + onClick?: (status: ConnectionStatusKind) => void; +}> = ({ status, disabled, isError, onClick }) => { + const [hover, setHover] = React.useState(false); + + const handleClick = React.useCallback(() => { + if (disabled === true) { + return; + } + if (onClick) { + onClick(status); + } + }, [status, disabled]); + + const statusText = getStatusText(status, hover); + const statusTextColor = isError ? '#40475C' : '#FFF'; + const statusFillColor = getStatusFillColor(status, hover, Boolean(isError)); + + return ( + + !disabled && setHover(true)} + onMouseLeave={() => !disabled && setHover(false)} + > + + + + + + + + + + {status === 'connected' && hover ? ( + + ) : ( + + )} + + {statusText} + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/nym-connect/mobile/src/components/ConnectionStats.tsx b/nym-connect/mobile/src/components/ConnectionStats.tsx new file mode 100644 index 0000000000..f17833e66d --- /dev/null +++ b/nym-connect/mobile/src/components/ConnectionStats.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Box, Typography } from '@mui/material'; +import prettyBytes from 'pretty-bytes'; + +export interface ConnectionStatsItem { + label: string; + rateBytesPerSecond: number; + totalBytes: number; +} + +const FONT_SIZE = '14px'; + +export const ConnectionStats: FCWithChildren<{ + stats: ConnectionStatsItem[]; +}> = ({ stats }) => ( + +
+ {stats.map((stat) => ( + + {stat.label} + + ))} +
+
+ {stats.map((stat) => ( + + {formatRate(stat.rateBytesPerSecond)} + + ))} +
+
+ {stats.map((stat) => ( + + {formatTotal(stat.totalBytes)} + + ))} +
+
+); + +export function formatRate(bytesPerSecond: number): string { + return `${prettyBytes(bytesPerSecond)}/s`; +} + +export function formatTotal(totalBytes: number): string { + return prettyBytes(totalBytes); +} diff --git a/nym-connect/mobile/src/components/ConnectionStatus.tsx b/nym-connect/mobile/src/components/ConnectionStatus.tsx new file mode 100644 index 0000000000..4ee49c5e14 --- /dev/null +++ b/nym-connect/mobile/src/components/ConnectionStatus.tsx @@ -0,0 +1,134 @@ +import React, { useState } from 'react'; +import { Box, CircularProgress, Typography } from '@mui/material'; +import { DateTime } from 'luxon'; +import { ErrorOutline, InfoOutlined } from '@mui/icons-material'; +import { ConnectionStatusKind, GatewayPerformance } from '../types'; +import { ServiceProvider } from '../types/directory'; +import { GatwayWarningInfo, ServiceProviderInfo } from './TooltipInfo'; +import { InfoModal } from './InfoModal'; + +const FONT_SIZE = '14px'; +const FONT_STYLE = 'normal'; + +const ConnectionStatusContent: FCWithChildren<{ + status: ConnectionStatusKind; + serviceProvider?: ServiceProvider; + gatewayError: boolean; +}> = ({ status, serviceProvider, gatewayError }) => { + const [showSpInfo, setShowSpInfo] = useState(false); + const [showGatewayWarn, setShowGatewayWarn] = useState(false); + + if (gatewayError) { + return ( + <> + {serviceProvider && ( + setShowGatewayWarn(false)} + > + + + )} + setShowGatewayWarn(true)} + > + + + Gateway has issues + + + + ); + } + switch (status) { + case 'connected': + return ( + <> + {serviceProvider && ( + setShowSpInfo(false)}> + + + )} + setShowSpInfo(true)} + justifyContent="center" + sx={{ cursor: 'pointer' }} + > + + + Connected to Nym Mixnet + + + + ); + case 'disconnected': + return ( + + Connect to the mixnet + + ); + case 'disconnecting': + return ( + + + + Disconnecting... + + + ); + case 'connecting': + return ( + + + + Connecting... + + + ); + + default: + return null; + } +}; + +export const ConnectionStatus: FCWithChildren<{ + status: ConnectionStatusKind; + gatewayPerformance?: GatewayPerformance; + connectedSince?: DateTime; + serviceProvider?: ServiceProvider; +}> = ({ status, serviceProvider, gatewayPerformance }) => { + const color = status === 'connected' || status === 'disconnecting' ? '#21D072' : 'white'; + + return ( + + + + ); +}; diff --git a/nym-connect/mobile/src/components/ConntectionTimer.tsx b/nym-connect/mobile/src/components/ConntectionTimer.tsx new file mode 100644 index 0000000000..c7b341ecfe --- /dev/null +++ b/nym-connect/mobile/src/components/ConntectionTimer.tsx @@ -0,0 +1,28 @@ +import React, { useEffect } from 'react'; +import { Stack, Typography } from '@mui/material'; +import { DateTime } from 'luxon'; + +export const ConnectionTimer = ({ connectedSince }: { connectedSince?: DateTime }) => { + const [duration, setDuration] = React.useState(); + useEffect(() => { + const intervalId = setInterval(() => { + if (connectedSince) { + setDuration(DateTime.now().diff(connectedSince).toFormat('hh:mm:ss')); + } + }, 500); + return () => { + clearInterval(intervalId); + }; + }, [connectedSince]); + + return ( + + + Connection time + + + {duration || '00:00:00'} + + + ); +}; diff --git a/nym-connect/mobile/src/components/CopyToClipboard.tsx b/nym-connect/mobile/src/components/CopyToClipboard.tsx new file mode 100644 index 0000000000..7a48662190 --- /dev/null +++ b/nym-connect/mobile/src/components/CopyToClipboard.tsx @@ -0,0 +1,69 @@ +import React, { useEffect, useState } from 'react'; +import { Button, IconButton, Tooltip } from '@mui/material'; +import { Check, ContentCopy } from '@mui/icons-material'; +import { clipboard } from '@tauri-apps/api'; + +export const CopyToClipboard = ({ + text = '', + light, + iconButton, +}: { + text?: string; + light?: boolean; + iconButton?: boolean; +}) => { + const [copied, setCopied] = useState(false); + + const handleCopy = async (textToCopy: string) => { + try { + if (clipboard) { + await clipboard.writeText(textToCopy); + } else { + await navigator.clipboard.writeText(textToCopy); + } + setCopied(true); + } catch (e) { + console.log(`failed to copy: ${e}`); + } + }; + + useEffect(() => { + let timer: NodeJS.Timeout; + if (copied) { + timer = setTimeout(() => { + setCopied(false); + }, 2000); + } + return () => clearTimeout(timer); + }, [copied]); + + if (iconButton) + return ( + + handleCopy(text)} + size="small" + sx={{ + color: (theme) => (light ? theme.palette.common.white : theme.palette.nym.background.dark), + }} + > + {!copied ? : } + + + ); + + return ( + + ); +}; diff --git a/nym-connect/mobile/src/components/CustomTitleBar.tsx b/nym-connect/mobile/src/components/CustomTitleBar.tsx new file mode 100644 index 0000000000..ae6f7f8255 --- /dev/null +++ b/nym-connect/mobile/src/components/CustomTitleBar.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { ArrowBack, Menu } from '@mui/icons-material'; +import { Box, IconButton, Typography } from '@mui/material'; +// TODO since the structure refactor for NC this import fails +// import { NymWordmark } from '@nymproject/react/logo/NymWordmark'; +import { useNavigate } from 'react-router-dom'; + +const customTitleBarStyles = { + titlebar: { + background: '#1D2125', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + padding: '16px', + paddingBottom: '0px', + borderTopLeftRadius: '12px', + borderTopRightRadius: '12px', + }, +}; + +const CustomButton = ({ Icon, onClick }: { Icon: React.JSXElementConstructor; onClick: () => void }) => ( + + + +); + +const MenuIcon = () => { + const navigate = useNavigate(); + return navigate('/menu')} />; +}; + +const ArrowBackIcon = ({ onBack }: { onBack?: () => void }) => { + const navigate = useNavigate(); + const handleBack = () => { + onBack?.(); + navigate(-1); + }; + return ; +}; + +const getTitleIcon = (path: string) => { + if (path !== '/') { + const title = path.split('/').slice(-1); + return ( + + {title} + + ); + } + + // TODO return ; + return ( + + NYM + + ); +}; + +export const CustomTitleBar = ({ path = '/', onBack }: { path?: string; onBack?: () => void }) => ( + + {/* set width to keep logo centered */} + {path === '/' ? : } + {getTitleIcon(path)} + {path !== '/' && } + +); diff --git a/nym-connect/mobile/src/components/Error.tsx b/nym-connect/mobile/src/components/Error.tsx new file mode 100644 index 0000000000..f582536b6b --- /dev/null +++ b/nym-connect/mobile/src/components/Error.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { FallbackProps } from 'react-error-boundary'; +import { Alert, AlertTitle, Button } from '@mui/material'; + +export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => ( +
+ + {error.name} + {error.message} + + + Stack trace + {error.stack} + + +
+); diff --git a/nym-connect/mobile/src/components/ErrorFallback.tsx b/nym-connect/mobile/src/components/ErrorFallback.tsx new file mode 100644 index 0000000000..f582536b6b --- /dev/null +++ b/nym-connect/mobile/src/components/ErrorFallback.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { FallbackProps } from 'react-error-boundary'; +import { Alert, AlertTitle, Button } from '@mui/material'; + +export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => ( +
+ + {error.name} + {error.message} + + + Stack trace + {error.stack} + + +
+); diff --git a/nym-connect/mobile/src/components/ExperimentalWarning.tsx b/nym-connect/mobile/src/components/ExperimentalWarning.tsx new file mode 100644 index 0000000000..59595d389e --- /dev/null +++ b/nym-connect/mobile/src/components/ExperimentalWarning.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Box, Typography } from '@mui/material'; + +export const ExperimentalWarning = () => ( + + + This is experimental software. + + + Do not rely on it for strong anonymity (yet). + + +); diff --git a/nym-connect/mobile/src/components/Growth/content/assets/matrix.webp b/nym-connect/mobile/src/components/Growth/content/assets/matrix.webp new file mode 100644 index 0000000000..2b1517166c Binary files /dev/null and b/nym-connect/mobile/src/components/Growth/content/assets/matrix.webp differ diff --git a/nym-connect/mobile/src/components/HelpPage.tsx b/nym-connect/mobile/src/components/HelpPage.tsx new file mode 100644 index 0000000000..3ec3e3e940 --- /dev/null +++ b/nym-connect/mobile/src/components/HelpPage.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Stack, Typography } from '@mui/material'; +import { HelpPageActions } from './HelpPageActions'; +import { HelpImage } from './HelpPageImage'; +import { StepIndicator } from './HelpPageStepIndicator'; + +export const HelpPage = ({ + step, + totalSteps, + description, + img, + onNext, + onPrev, +}: { + step: number; + totalSteps: number; + description: string; + img: any; + onNext?: () => void; + onPrev?: () => void; +}) => ( + + + + + How to connect guide {step}/{totalSteps} + + + {description} + + + + + +); diff --git a/nym-connect/mobile/src/components/HelpPageActions.tsx b/nym-connect/mobile/src/components/HelpPageActions.tsx new file mode 100644 index 0000000000..13cba78fd2 --- /dev/null +++ b/nym-connect/mobile/src/components/HelpPageActions.tsx @@ -0,0 +1,20 @@ +import { ArrowBack, ArrowForward } from '@mui/icons-material'; +import { Box, Button } from '@mui/material'; +import React from 'react'; + +export const HelpPageActions = ({ onNext, onPrev }: { onNext?: () => void; onPrev?: () => void }) => ( + + {onPrev ? ( + + ) : ( +
+ )} + {onNext && ( + + )} + +); diff --git a/nym-connect/mobile/src/components/HelpPageImage.tsx b/nym-connect/mobile/src/components/HelpPageImage.tsx new file mode 100644 index 0000000000..69fa38ca31 --- /dev/null +++ b/nym-connect/mobile/src/components/HelpPageImage.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export const HelpImage = ({ img, imageDescription }: { img: string; imageDescription: string }) => ( + {imageDescription} +); diff --git a/nym-connect/mobile/src/components/HelpPageStepIndicator.tsx b/nym-connect/mobile/src/components/HelpPageStepIndicator.tsx new file mode 100644 index 0000000000..fc108425ab --- /dev/null +++ b/nym-connect/mobile/src/components/HelpPageStepIndicator.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Box, Stack } from '@mui/material'; + +const Step = ({ highlight }: { highlight: boolean }) => ( + +); + +export const StepIndicator = ({ step }: { step: number }) => ( + + + = 2} /> + = 3} /> + +); diff --git a/nym-connect/mobile/src/components/InfoModal.tsx b/nym-connect/mobile/src/components/InfoModal.tsx new file mode 100644 index 0000000000..07f98769e5 --- /dev/null +++ b/nym-connect/mobile/src/components/InfoModal.tsx @@ -0,0 +1,69 @@ +import { Close, ErrorOutline } from '@mui/icons-material'; +import { Box, IconButton, Modal, Theme, Typography } from '@mui/material'; +import React from 'react'; + +const styles = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 200, + bgcolor: '#292E34', + p: 1.5, + borderRadius: 0.5, + height: 'fit-content', + border: (theme: Theme) => `1px solid ${theme.palette.grey[700]}`, +}; + +const ModalTitle = ({ title, withCloseIcon }: { title: string; withCloseIcon: boolean }) => ( + + + + {title} + + +); + +const ModalBody = ({ description, children }: { description: string; children?: React.ReactElement }) => ( + + {children} + + {description} + + +); + +export const InfoModal = ({ + title, + description, + show, + children, + Action, + onClose, +}: { + title: string; + description: string; + show: boolean; + children?: React.ReactElement; + Action?: React.ReactNode; + onClose?: () => void; +}) => ( + + + {onClose && ( + + + + + + )} + + {children} + {Action && ( + + {Action} + + )} + + +); diff --git a/nym-connect/mobile/src/components/IpAddressAndPort.tsx b/nym-connect/mobile/src/components/IpAddressAndPort.tsx new file mode 100644 index 0000000000..4ecf356d18 --- /dev/null +++ b/nym-connect/mobile/src/components/IpAddressAndPort.tsx @@ -0,0 +1,55 @@ +import { Box, Typography } from '@mui/material'; +import React from 'react'; +import { styled } from '@mui/system'; + +const IpAddressAndPortContainer = styled('div')({ + '.hoverAddressCopy:hover': { + cursor: 'pointer', + textDecoration: 'underline', + textDecorationColor: '#FB6E4E', + textDecorationThickness: '2px', + textUnderlineOffset: '4px', + }, +}); + +export const IpAddressAndPort: FCWithChildren<{ + label: string; + ipAddress: string; + port: number; +}> = ({ label, ipAddress, port }) => { + const [ipAddressCopied, setIpAddressCopied] = React.useState(false); + const [portCopied, setPortCopied] = React.useState(false); + + React.useEffect(() => { + if (ipAddressCopied) { + setTimeout(() => setIpAddressCopied(false), 2000); + } + }, [ipAddressCopied]); + + React.useEffect(() => { + if (portCopied) { + setTimeout(() => setPortCopied(false), 2000); + } + }, [portCopied]); + + return ( + + + + {label} + + + Port + + + + + {ipAddress} + + + {port} + + + + ); +}; diff --git a/nym-connect/mobile/src/components/IpAddressAndPortModal.tsx b/nym-connect/mobile/src/components/IpAddressAndPortModal.tsx new file mode 100644 index 0000000000..fcb0f1dfaa --- /dev/null +++ b/nym-connect/mobile/src/components/IpAddressAndPortModal.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Box, Typography } from '@mui/material'; +import { InfoModal } from './InfoModal'; +import { CopyToClipboard } from './CopyToClipboard'; + +const FONT_SIZE = '12px'; +const FONT_COLOR = 'grey.400'; + +export const IpAddressAndPortModal = ({ + show, + ipAddress, + port, + onClose, +}: { + show: boolean; + ipAddress: string; + port: number; + onClose: () => void; +}) => ( + + + + Paste below values in the proxy settings of your app + + + Socks5 address + + + {ipAddress} + + + + + Port + + + {port} + + + + +); diff --git a/nym-connect/mobile/src/components/NeedHelp.tsx b/nym-connect/mobile/src/components/NeedHelp.tsx new file mode 100644 index 0000000000..12d9587a5f --- /dev/null +++ b/nym-connect/mobile/src/components/NeedHelp.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Box, Button, Typography } from '@mui/material'; +import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; + +const HELP_URL = 'https://docs.nymtech.net'; + +export const NeedHelp: FCWithChildren = () => ( + + + +); diff --git a/nym-connect/mobile/src/components/PowerButton/PowerButton.tsx b/nym-connect/mobile/src/components/PowerButton/PowerButton.tsx new file mode 100644 index 0000000000..bc399e70b9 --- /dev/null +++ b/nym-connect/mobile/src/components/PowerButton/PowerButton.tsx @@ -0,0 +1,149 @@ +import React, { useCallback } from 'react'; +import { ConnectionStatusKind } from 'src/types'; +import './power-button.css'; + +const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => { + if (isError && hover) { + return '#21D072'; + } + if (isError) { + return '#40475C'; + } + + switch (status) { + case 'disconnected': + if (hover) { + return '#FFF'; + } + return '#BBB'; + case 'connecting': + return '#FFF'; + case 'disconnecting': + return '#FFF'; + default: + // connected + if (hover) { + return '#E43E3E'; + } + return '#21D072'; + } +}; + +export const PowerButton: FCWithChildren<{ + onClick?: (status: ConnectionStatusKind) => void; + isError?: boolean; + disabled?: boolean; + status: ConnectionStatusKind; + busy?: boolean; +}> = ({ onClick, disabled, status, isError }) => { + const [hover, setHover] = React.useState(false); + + const handleClick = () => { + if (disabled === true) { + return; + } + if (onClick) { + onClick(status); + } + }; + + const statusFillColor = getStatusFillColor(status, hover, Boolean(isError)); + + const getClassName = useCallback(() => { + if (hover) { + switch (status) { + case 'disconnected': + return 'expand'; + default: + return 'contract'; + } + } + if (!hover) { + switch (status) { + case 'connected': + return 'expand'; + default: + return 'contract'; + } + } + return 'contract'; + }, [status, hover]); + + const buttonPulse = () => { + if (status === 'connecting' || status === 'disconnecting') return 'pulse'; + return undefined; + }; + + let statusText: string; + switch (status) { + case 'connected': + statusText = 'stop'; + break; + case 'disconnected': + statusText = 'start'; + break; + default: + statusText = ''; + } + + return ( + !disabled && setHover(true)} + onMouseLeave={() => !disabled && setHover(false)} + > + + + + + + + + + + + + + + + + + + + + + + + {statusText.toUpperCase()} + + + + + + + + + + ); +}; diff --git a/nym-connect/mobile/src/components/PowerButton/power-button.css b/nym-connect/mobile/src/components/PowerButton/power-button.css new file mode 100644 index 0000000000..b73d01ca32 --- /dev/null +++ b/nym-connect/mobile/src/components/PowerButton/power-button.css @@ -0,0 +1,72 @@ +#ring-expand { + animation-name: rings-expand; + animation-duration: 0.4s; + animation-timing-function: ease-in-out; + animation-play-state: paused; +} + +#ring-one.expand { + opacity: 0.3; + transition: opacity 0.1s ease-in-out; +} + +#ring-two.expand { + opacity: 0.2; + transition: opacity 0.2s ease-in-out; +} + +#ring-three.expand { + opacity: 0.1; + transition: opacity 0.3s ease-in-out; +} + +#ring-four.expand { + opacity: 0.05; + transition: opacity 0.4s ease-in-out; +} + +#ring-one.contract { + opacity: 0; + transition: opacity 0.4s; + transition-delay: 0.1s; +} + +#ring-two.contract { + opacity: 0; + transition: opacity 0.3s; + transition-delay: 0.1s; +} + +#ring-three.contract { + opacity: 0; + transition: opacity 0.1s; + transition-delay: 0.1s; +} + +#ring-four.contract { + opacity: 0; + transition: opacity 0.1s; + transition-delay: 0.1s; +} + +circle, +path, text { + transition: stroke 0.5s, fill 0.5s; +} + +.pulse { + animation-name: pulse; + animation-duration: 0.5s; + animation-iteration-count: infinite; + animation-direction: alternate; +} + +@keyframes pulse { + from { + opacity: 1; + } + + to { + opacity: 0.3; + } +} diff --git a/nym-connect/mobile/src/components/ServiceProviderPopup.tsx b/nym-connect/mobile/src/components/ServiceProviderPopup.tsx new file mode 100644 index 0000000000..660fee4134 --- /dev/null +++ b/nym-connect/mobile/src/components/ServiceProviderPopup.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Autocomplete, Box, Dialog, DialogProps, TextField, Typography } from '@mui/material'; +import { Service, ServiceProvider, Services } from '../types/directory'; + +export const ServiceProviderPopup: FCWithChildren< + DialogProps & { services: Services; onServiceProviderChanged: (sp?: ServiceProvider, s?: Service) => void } +> = ({ services, onServiceProviderChanged, ...dialogProps }) => { + const options = services.flatMap((s) => + s.items.map((sp) => ({ id: `${s.id}-${sp.id}`, title: sp.description, service: s, sp })), + ); + return ( + + + // filterOptions.filter((o) => o.title.toLowerCase().includes(inputValue.toLowerCase())) + // } + options={options} + groupBy={(option) => option.service.description} + getOptionLabel={(option) => option.title} + onChange={(event, value) => onServiceProviderChanged(value?.sp, value?.service)} + isOptionEqualToValue={(option, value) => option.id.toLowerCase() === value?.id.toLowerCase()} + renderOption={(props, option) => ( + img': { mr: 2, flexShrink: 0 } }} {...props} fontSize="small"> + + {option.id} + +

{option.title}

+
+ )} + renderInput={(params) => } + /> +
+ ); +}; diff --git a/nym-connect/mobile/src/components/ServiceProviderSelector.stories.tsx b/nym-connect/mobile/src/components/ServiceProviderSelector.stories.tsx new file mode 100644 index 0000000000..c7df76ea4a --- /dev/null +++ b/nym-connect/mobile/src/components/ServiceProviderSelector.stories.tsx @@ -0,0 +1,342 @@ +import * as React from 'react'; +import { ComponentMeta } from '@storybook/react'; +import { Box } from '@mui/material'; +import { ServiceProviderSelector } from './ServiceProviderSelector'; +import { Services } from '../types/directory'; + +export default { + title: 'Components/Service Provider Selector', + component: ServiceProviderSelector, +} as ComponentMeta; + +const width = 240; + +export const Loading = () => ( + + + +); + +const services: Services = JSON.parse(`[ + { + "id": "keybase", + "description": "Keybase", + "items": [ + { + "id": "nym-keybase", + "description": "Nym Keybase Service Provider", + "address": "Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf", + "gateway": "Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf" + }, + { + "id": "shipyard-keybase-1", + "description": "Nym Keybase Service Provider", + "address": "D55ksecHzY6vAeqk8MCTzCfj2pqwJeKCKZCUUGnwGnn3.FS42vXS5a6GNTb1qk3aVk5mjSiJCAuawbBVyQZZVfhvt@DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn", + "gateway": "DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn" + }, + { + "id": "shipyard-keybase-2", + "description": "Nym Keybase Service Provider", + "address": "DFdDtW7LNBATxQ4ef3jNbqs3cRE8b9wDZTCctHCQRULa.4AbKiTNVUwYFWHhy98o5pT9dELiUrkXoJQ9wHqPgf6GV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN", + "gateway": "GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN" + }, + { + "id": "shipyard-keybase-3", + "description": "Nym Keybase Service Provider", + "address": "6Y1HE1jJ92P9yoHer11TR4A2NdZePrLGaBNFf65MnYGe.FwXoh217odQDWNmViqzNX28fauYrjB3PYLrVvpqnQrX4@5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz", + "gateway": "5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz" + }, + { + "id": "shipyard-keybase-4", + "description": "Nym Keybase Service Provider", + "address": "3zzhLtWvaJgn755MkRckG5aRnoTZich8ASn395iSsTgj.J1R5VuxXbh2eNHiaRbrwbKGXrrEQcHKLdzf8eg9HTB6q@3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj", + "gateway": "3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj" + }, + { + "id": "shipyard-keybase-5", + "description": "Nym Keybase Service Provider", + "address": "CHuXdZJYQ8xH7ekgN9gAuVtQ7ZikjjHEZY5BSN7yc5mN.29dFvqicKQQQvoX1vup44mspmc249RH5xgLibWMwTYGT@CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9", + "gateway": "CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9" + } + ] + }, + { + "id": "electrum", + "description": "Electrum Wallet", + "items": [ + { + "id": "nym-electrum", + "description": "Nym Electrum Service Provider", + "address": "DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh", + "gateway": "2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh" + }, + { + "id": "shipyard-electrum-1", + "description": "Nym Electrum Service Provider", + "address": "8Tb73cFQpXCLpgxEA2VSDru2hHrcZ3KQcyMsGbxcTjBp.4x5tu66k8YkHk4tYac1qwEFbNq5WsKiX5kR51q5KKH88@4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd", + "gateway": "4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd" + }, + { + "id": "shipyard-electrum-2", + "description": "Nym Electrum Service Provider", + "address": "GR6z31MwCsvxHrnvvVN1Cpasd8aQ1giwQqPTZM9dN7VH.5rEiqakSPDrBtKmvpU8Shnhz6gRM85JLoB7AX4h7PJYr@5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD", + "gateway": "5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD" + } + ] + }, + { + "id": "telegram", + "description": "Telegram", + "items": [ + { + "id": "shipyard-telegram-2", + "description": "Nym Telegram Service Provider", + "address": "C4w6ewbQtoaZEeoaaNw1xVASChqo4WVjNfuYEUFjZxpc.8F1D7rQXf2jGoj1Ken7PiGDM8HS2Ug79wSoc9nZ1iqh1@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve", + "gateway": "62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve" + }, + { + "id": "shipyard-telegram-3", + "description": "Nym Telegram Service Provider", + "address": "DStL3BEUZuQZfbij1KAY3BvJh8rC5jpr9mc6AQ6aTLUu.Ax9foYaKfFgX6g8y393GoNpKkKrnDGFGRZwxDv9R7X6M@FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE", + "gateway": "FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE" + }, + { + "id": "shipyard-telegram-4", + "description": "Nym Telegram Service Provider", + "address": "8gRdGTzsDxYzpasRQhsRg59MCgNNhnfag2oFfwwZPXnB.DtDrGz7ScVm4o7sN4K3CYUJveYgz7fcXELBVLNDfMS9Y@3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh", + "gateway": "3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh" + }, + { + "id": "shipyard-telegram-5", + "description": "Nym Telegram Service Provider", + "address": "AR3oEM6Uvmfs6fyddwSehoBUKCFxz7MdFi4z7aahuHuY.3ZKapg9A3Py1PXhyLbCJr8ZbJsEV6NZdN1WJaGGut5tj@EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp", + "gateway": "EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp" + }, + { + "id": "shipyard-telegram-6", + "description": "Nym Telegram Service Provider", + "address": "7n1BYhsXSwcr8Qim8AqZTAodqFia4QG6T7CRc1ihQHpv.7o4trpGqu2LHMUiXc3dddgfGET1CFFcAK9gKYoHoSn5e@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr", + "gateway": "BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr" + }, + { + "id": "shipyard-telegram-7", + "description": "Nym Telegram Service Provider", + "address": "Gv4TWhUKrvJfqh1jBRPGEQrikNZvZse2kS3ZgN9Z2nAZ.7KGPaaqUEum2C59jLvw7f8Ug8a48YuZdjjZu3t4JES4U@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi", + "gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi" + }, + { + "id": "shipyard-telegram-8", + "description": "Nym Telegram Service Provider", + "address": "8Mqgp12cpF6FSXMeqzxgFgQXvTSapyNqGAi5wy7ub4ge.7z7PDsiJGiGxGz4p77v5L5fZhXBJ5qNZ8CgJwYNr6H6J", + "gateway": "3zd3wrCK8Dz5TXrcvk5dG5s9EEdf4Ck1v9VgBPMMFVkR" + }, + { + "id": "shipyard-telegram-9", + "description": "Nym Telegram Service Provider", + "address": "F3N5eiPDZcGFC985Go4Mpv8p9uxFD1L3jRUdrLCbrZLm.EyTxWwwTwYpPrJBmc97GLd1LpUAphjptS5y1ed182bGk@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ", + "gateway": "GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ" + }, + { + "id": "shipyard-telegram-10", + "description": "Nym Telegram Service Provider", + "address": "G7y7e1nVBr8fmQSzdeAxXnCmmmJb5k8N3E8LBV31KE5g.GRRUCj6t6cCUUjakmTWzidMLiYA7EdCedKnup8osaBC6@AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc", + "gateway": "AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc" + }, + { + "id": "shipyard-telegram-11", + "description": "Nym Telegram Service Provider", + "address": "2kq9Z7RyDZtb8kxXjyP3ZT8VMWHg6JXFDChGuuNBk7Hw.F5XYbBaGSoF8qAo8faPcaNRPHEq3Y25BDcwESeabUS9S@HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM", + "gateway": "HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM" + }, + { + "id": "shipyard-telegram-12", + "description": "Nym Telegram Service Provider", + "address": "GegdtpNzYj4QCgpih9Kxv7ZVZxmVdxYHsDkiPsbT71XG.E8xtE8mrapjzFtyuziZSrsScAKhwZMH5wNpKWtKfzJ5Y@9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J", + "gateway": "9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J" + }, + { + "id": "shipyard-telegram-13", + "description": "Nym Telegram Service Provider", + "address": "4SsrDQeEtG3mpeD9nN5CDdGaCsxFvNeYMhoviDzNNB9f.GyqG6iK5rBvhe3HXLR11m6ULpf13ATgYvkkidLmteDLs@5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS", + "gateway": "5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS" + }, + { + "id": "shipyard-telegram-14", + "description": "Nym Telegram Service Provider", + "address": "9JoHRu2RrSD1fjbj9NSTASgjv9Szep7Nhd9L2PywxbBi.AZhAUDNX6iH8BqXyR5c7TJuzpwMEvDXrabNLGuRukvVf@9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS", + "gateway": "9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS" + }, + { + "id": "shipyard-telegram-15", + "description": "Nym Telegram Service Provider", + "address": "3K174ijjXqCkhMDT9xLcqjS4MXk2QsqZt4PdgNcuUrnn.BNnHnQmWoj6Uo6kkS1QkPqsdHaXrcwyR9F6MnnzDkZJL@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi", + "gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi" + }, + { + "id": "shipyard-telegram-16", + "description": "Nym Telegram Service Provider", + "address": "BqX5Q3MEcbTnM39hUswQchLW68SrqbhL8K5ucrLmtP39.AWrVsFoVC9s6KjdpcasATmZPA3GtMsUxcfHpAkuNdtFG@Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS", + "gateway": "Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS" + }, + { + "id": "shipyard-telegram-17", + "description": "Nym Telegram Service Provider", + "address": "2tQxccgcqdkuUvLqgiEkEN4rNRZ5QknygnKAFcS4gfoe.EVrY5q5sqDqBUbS3wHsRRZhk2MAw1S17hNoH1Bicyv7n@DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom", + "gateway": "DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom" + }, + { + "id": "shipyard-telegram-18", + "description": "Nym Telegram Service Provider", + "address": "8YG1rcEauJA814Nd7hSxjNe2UrRwrGsrXTm1Cmd3gRrU.FxYaYqpNN8PciNsySs3zYPrTB1J8AYUu9DBsM2vVDDfF@7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog", + "gateway": "7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog" + }, + { + "id": "shipyard-telegram-19", + "description": "Nym Telegram Service Provider", + "address": "HPiXADVFLwLQPNpPtyYefzvYntC6tp9UJ5fJZGfkqvDt.2EUUxmeT3AiaUzAcE5SyXRAk8a2JXBkRz4B8McSdkrST@9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD", + "gateway": "9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD" + }, + { + "id": "shipyard-telegram-20", + "description": "Nym Telegram Service Provider", + "address": "2QLnEEnTmf2NRWtcQPWBeRcg7Hej5WSPWRWwtTpEEZWF.BheS78ozc8ngvhsXNNnshdJzpoYsmEvhfn3WKUYF5dRU@C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF", + "gateway": "C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF" + }, + { + "id": "shipyard-telegram-21", + "description": "Nym Telegram Service Provider", + "address": "FuBbnwiANfaXZnn683jBapK5XVm5ttgZSykU3vqPSHoD.94MFGv1VcBLTkRwzBDQUkWjvqtZYVBrJg2Q8JGbizcib@CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK", + "gateway": "CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK" + }, + { + "id": "shipyard-telegram-22", + "description": "Nym Telegram Service Provider", + "address": "9EbQx5jQznSVbftFom7sqUSHAACrsfvMhrzhaFt4A3SZ.D1FQCirL4YKwfcmtMGvB5Rugt5sAzGEhdSjJ3bHVQRZ@7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4", + "gateway": "7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4" + }, + { + "id": "shipyard-telegram-24", + "description": "Nym Telegram Service Provider", + "address": "6Umawwvf551VyB3Ko46NgKLqJdTFJeToCM67mrTmM3G.3A4sesBac4KGuMTFjvYBwLpksMJvbMbteGJQgmm4PV4Y@AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR", + "gateway": "AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR" + }, + { + "id": "shipyard-telegram-25", + "description": "Nym Telegram Service Provider", + "address": "CDtxTeoyqq83JpV9G8cR5HRHRdMMaVspQsCwH3Qnajt3.F5EHK9HFcdGrE2hqA7bK9AUmkbihujYDhtNNqHKxW765@BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH", + "gateway": "BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH" + }, + { + "id": "shipyard-telegram-26", + "description": "Nym Telegram Service Provider", + "address": "HukZkLG2DoarQEqaoDLuqW1GFf2NSHDUMGBZiyJGRYJD.9GyU8wPsyzcvRjcyk8hiNpTJbXCmq5F3VoVhFBZYuHR3@GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN", + "gateway": "GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN" + }, + { + "id": "shipyard-telegram-27", + "description": "Nym Telegram Service Provider", + "address": "773y8iMVJiRk4dRbjQzkJVbrei4TwkePNE5WTEttt77d.3Mw47C9XZj3oAzk1iSqC5Y36tbBsjtaTtdgaHM3Zsdma@7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv", + "gateway": "7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv" + }, + { + "id": "shipyard-telegram-28", + "description": "Nym Telegram Service Provider", + "address": "6jQJEorCu7YiP9HdDaMeHxcNhxeNmZ1kpd836GnqLZX.HsJqEmNTszGecsKqFB37i84nBXxqf4ETgrKmKmBvMGHC@FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE", + "gateway": "FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE" + }, + { + "id": "shipyard-telegram-29", + "description": "Nym Telegram Service Provider", + "address": "BiCSyovpFMuSnTvF2TdiuZwrytXDrd9AH47ZMcCxscVC.G9YpdicA9BBNoVHDgjWjgt17wv5WYKWcbE3vPJJVpSJD@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ", + "gateway":"GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ" + }, + { + "id": "shipyard-telegram-30", + "description": "Nym Telegram Service Provider", + "address": "AQRRAs9oc8QWXAFBs44YhCKUny7AyLsfLy91pwmGgxuf.CWUKoKA1afSKyw5BnFJJg19UDgnaVATupsFhQpyTEBHJ@EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w", + "gateway": "EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w" + }, + { + "id": "shipyard-telegram-31", + "description": "Nym Telegram Service Provider", + "address": "6YqjAZK3Pr1ngiBLcDkotboB5WiN6k6NPpbXvShH4pR5.9Ss6VW3Xbyi8LuxduNNwnXEv9njHCQ2PLSP1UK6tsoa5@42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD", + "gateway": "42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD" + }, + { + "id": "shipyard-telegram-32", + "description": "Nym Telegram Service Provider", + "address": "EmYWLeybmj86Vzr62vxuZ3T15jwMNHggzK7sQwid96yp.GyaF9WprSr56cxUdGf5TpcUvAjb2VbAr8CVBrmBUYAaw@GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC", + "gateway": "GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC" + }, + { + "id": "shipyard-telegram-33", + "description": "Nym Telegram Service Provider", + "address": "4PDb96cck5btTj6G7rsomqwHJsp4qu8uPvFCbwHfjFUx.C5dKbaoakH7egsZvAueRbwLFbmxnQaVMeSr6QTMpuBAA@58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY", + "gateway": "58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY" + }, + { + "id": "shipyard-telegram-34", + "description": "Nym Telegram Service Provider", + "address": "BeZbeMf9vcpUf368qDd85dtLwXLj4Ee5bsHMB2fUD8uX.HELVbppkwU1jmzUAUrCEbHeJfVciSeo8VGAkbJSpwxsb@ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY", + "gateway":"ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY" + }, + { + "id": "shipyard-telegram-35", + "description": "Nym Telegram Service Provider", + "address": "Bp4JRFyf7GB9L9J95AqMPnz9zbGmPnViA5fDXKeNraLJ.D6CTdcjJVxDmH2UQvzXuPWg9Se9xXYe76uDMypXvhzd7@6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N", + "gateway": "6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N" + }, + { + "id": "shipyard-telegram-36", + "description": "Nym Telegram Service Provider", + "address": "91h7io6BGhVjbtC7dbbRcScyTJcTfnMsTQZ6aWMVsrWR.Epb4hANXCp8cGEY3wSgawux991ti9Z5Y1FHTMzAKFa6E@DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe", + "gateway": "DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe" + }, + { + "id": "shipyard-telegram-37", + "description": "Nym Telegram Service Provider", + "address": "Cy2wuwKpWZ3iWzKU3ZWL1qqcVfJ5Cq85dU7UHVWwv2gc.9AhC9b2zVcLnXLGriMdxjpsWJpq6iAdCavDi63udbL89@678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf", + "gateway": "678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf" + }, + { + "id": "shipyard-telegram-38", + "description": "Nym Telegram Service Provider", + "address": "GgUeUWW1NRSuquZYeZf3WkppE92EQUHJrFjNZtYU1jow.CSEjwrRi4f4uyw7N6L2LPKw2tB8spcMbFu99wHZzFZSj@77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm", + "gateway": "77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm" + }, + { + "id": "shipyard-telegram-39", + "description": "Nym Telegram Service Provider", + "address": "kz4zWwSkYiQxqxXFPNcGUByTPQWXascD9RfYsmSxY7n.ajp3SjbBVBjrU9nXpSQXAXzbb6EHJJyhbY6cc1ajayx@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYf", + "gateway": "HyS2UZtZX3kQXdazbdE99DhCjBXjbG61LC9QsmXwbxrU" + } + ] + }, + { + "id": "blockstream", + "description": "Blockstream Green", + "items": [ + { + "id": "nym-blockstream", + "description": "Nym Blockstream Green Service Provider", + "address": "GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW", + "gateway": "2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW" + } + ] + } +]`); +export const Loaded = () => ( + + + +); + +export const ServiceAlreadySelected = () => ( + + console.log('New service provider selected: ', serviceProvider)} + /> + +); diff --git a/nym-connect/mobile/src/components/ServiceProviderSelector.tsx b/nym-connect/mobile/src/components/ServiceProviderSelector.tsx new file mode 100644 index 0000000000..741465dac4 --- /dev/null +++ b/nym-connect/mobile/src/components/ServiceProviderSelector.tsx @@ -0,0 +1,147 @@ +import React, { useEffect, useMemo } from 'react'; +import { Box, CircularProgress, MenuItem, Stack, TextField, Tooltip, Typography } from '@mui/material'; +import { Service, ServiceProvider, Services } from '../types/directory'; +import { useTauriEvents } from '../utils'; + +type ServiceWithRandomSp = { + id: string; + description: string; + sp: ServiceProvider; +}; + +const defaultServiceValue = { id: '', description: '', items: [] }; + +export const ServiceProviderSelector: FCWithChildren<{ + onChange?: (serviceProvider?: ServiceProvider) => void; + services?: Services; + currentSp?: ServiceProvider; +}> = ({ services, currentSp, onChange }) => { + const [service, setService] = React.useState(defaultServiceValue); + const [serviceProvider, setServiceProvider] = React.useState(currentSp); + const [resetTrigger, setResetTrigger] = React.useState(new Date().toISOString()); + + const handleSelectSp = (newServiceProvider?: ServiceProvider) => { + if (newServiceProvider && newServiceProvider !== currentSp) { + setServiceProvider(newServiceProvider); + onChange?.(newServiceProvider); + } + }; + + // when the user clears local storage, reset the selector + useTauriEvents('help://clear-storage', () => { + setService(defaultServiceValue); + setServiceProvider(undefined); + onChange?.(undefined); + setResetTrigger(new Date().toISOString()); + }); + + useEffect(() => { + if (!serviceProvider && currentSp) { + setServiceProvider(currentSp); + } + }, [currentSp]); + + useEffect(() => { + if (services && serviceProvider) { + // retrieve the service corresponding to this service provider + + const match = services.find((s) => + s.items.some( + ({ id, address, gateway }) => + id === serviceProvider.id && address === serviceProvider.address && gateway === serviceProvider.gateway, + ), + ); + + if (match) { + setService(match); + } + } + }, [serviceProvider, services]); + + if (!services) { + return ( + + theme.palette.common.white}> + + Loading services... + + + ); + } + + const servicesWithRandomSp: ServiceWithRandomSp[] = useMemo( + () => + services.map(({ id, items, description }) => ({ + id, + description, + sp: items[Math.floor(Math.random() * items.length)], + })), + [services, resetTrigger], + ); + + if (!service) return null; + + return ( + + + {servicesWithRandomSp.map(({ id, description, sp }) => ( + handleSelectSp(sp)}> + + + {sp.id} + + + {sp.description} + + + Gateway {sp.gateway.slice(0, 10)}... + + + Provider {sp.address.slice(0, 10)}... + + + } + arrow + placement="top" + > + {description} + + + ))} + + + ); +}; diff --git a/nym-connect/mobile/src/components/ServiceSelector.stories.tsx b/nym-connect/mobile/src/components/ServiceSelector.stories.tsx new file mode 100644 index 0000000000..56be7c19ee --- /dev/null +++ b/nym-connect/mobile/src/components/ServiceSelector.stories.tsx @@ -0,0 +1,342 @@ +import * as React from 'react'; +import { ComponentMeta } from '@storybook/react'; +import { Box } from '@mui/material'; +import { ServiceSelector } from './ServiceSelector'; +import { Services } from '../types/directory'; + +export default { + title: 'Components/Service Selector', + component: ServiceSelector, +} as ComponentMeta; + +const width = 240; + +export const Loading = () => ( + + + +); + +const services: Services = JSON.parse(`[ + { + "id": "keybase", + "description": "Keybase", + "items": [ + { + "id": "nym-keybase", + "description": "Nym Keybase Service Provider", + "address": "Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf", + "gateway": "Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf" + }, + { + "id": "shipyard-keybase-1", + "description": "Nym Keybase Service Provider", + "address": "D55ksecHzY6vAeqk8MCTzCfj2pqwJeKCKZCUUGnwGnn3.FS42vXS5a6GNTb1qk3aVk5mjSiJCAuawbBVyQZZVfhvt@DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn", + "gateway": "DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn" + }, + { + "id": "shipyard-keybase-2", + "description": "Nym Keybase Service Provider", + "address": "DFdDtW7LNBATxQ4ef3jNbqs3cRE8b9wDZTCctHCQRULa.4AbKiTNVUwYFWHhy98o5pT9dELiUrkXoJQ9wHqPgf6GV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN", + "gateway": "GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN" + }, + { + "id": "shipyard-keybase-3", + "description": "Nym Keybase Service Provider", + "address": "6Y1HE1jJ92P9yoHer11TR4A2NdZePrLGaBNFf65MnYGe.FwXoh217odQDWNmViqzNX28fauYrjB3PYLrVvpqnQrX4@5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz", + "gateway": "5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz" + }, + { + "id": "shipyard-keybase-4", + "description": "Nym Keybase Service Provider", + "address": "3zzhLtWvaJgn755MkRckG5aRnoTZich8ASn395iSsTgj.J1R5VuxXbh2eNHiaRbrwbKGXrrEQcHKLdzf8eg9HTB6q@3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj", + "gateway": "3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj" + }, + { + "id": "shipyard-keybase-5", + "description": "Nym Keybase Service Provider", + "address": "CHuXdZJYQ8xH7ekgN9gAuVtQ7ZikjjHEZY5BSN7yc5mN.29dFvqicKQQQvoX1vup44mspmc249RH5xgLibWMwTYGT@CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9", + "gateway": "CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9" + } + ] + }, + { + "id": "electrum", + "description": "Electrum Wallet", + "items": [ + { + "id": "nym-electrum", + "description": "Nym Electrum Service Provider", + "address": "DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh", + "gateway": "2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh" + }, + { + "id": "shipyard-electrum-1", + "description": "Nym Electrum Service Provider", + "address": "8Tb73cFQpXCLpgxEA2VSDru2hHrcZ3KQcyMsGbxcTjBp.4x5tu66k8YkHk4tYac1qwEFbNq5WsKiX5kR51q5KKH88@4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd", + "gateway": "4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd" + }, + { + "id": "shipyard-electrum-2", + "description": "Nym Electrum Service Provider", + "address": "GR6z31MwCsvxHrnvvVN1Cpasd8aQ1giwQqPTZM9dN7VH.5rEiqakSPDrBtKmvpU8Shnhz6gRM85JLoB7AX4h7PJYr@5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD", + "gateway": "5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD" + } + ] + }, + { + "id": "telegram", + "description": "Telegram", + "items": [ + { + "id": "shipyard-telegram-2", + "description": "Nym Telegram Service Provider", + "address": "C4w6ewbQtoaZEeoaaNw1xVASChqo4WVjNfuYEUFjZxpc.8F1D7rQXf2jGoj1Ken7PiGDM8HS2Ug79wSoc9nZ1iqh1@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve", + "gateway": "62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve" + }, + { + "id": "shipyard-telegram-3", + "description": "Nym Telegram Service Provider", + "address": "DStL3BEUZuQZfbij1KAY3BvJh8rC5jpr9mc6AQ6aTLUu.Ax9foYaKfFgX6g8y393GoNpKkKrnDGFGRZwxDv9R7X6M@FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE", + "gateway": "FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE" + }, + { + "id": "shipyard-telegram-4", + "description": "Nym Telegram Service Provider", + "address": "8gRdGTzsDxYzpasRQhsRg59MCgNNhnfag2oFfwwZPXnB.DtDrGz7ScVm4o7sN4K3CYUJveYgz7fcXELBVLNDfMS9Y@3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh", + "gateway": "3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh" + }, + { + "id": "shipyard-telegram-5", + "description": "Nym Telegram Service Provider", + "address": "AR3oEM6Uvmfs6fyddwSehoBUKCFxz7MdFi4z7aahuHuY.3ZKapg9A3Py1PXhyLbCJr8ZbJsEV6NZdN1WJaGGut5tj@EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp", + "gateway": "EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp" + }, + { + "id": "shipyard-telegram-6", + "description": "Nym Telegram Service Provider", + "address": "7n1BYhsXSwcr8Qim8AqZTAodqFia4QG6T7CRc1ihQHpv.7o4trpGqu2LHMUiXc3dddgfGET1CFFcAK9gKYoHoSn5e@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr", + "gateway": "BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr" + }, + { + "id": "shipyard-telegram-7", + "description": "Nym Telegram Service Provider", + "address": "Gv4TWhUKrvJfqh1jBRPGEQrikNZvZse2kS3ZgN9Z2nAZ.7KGPaaqUEum2C59jLvw7f8Ug8a48YuZdjjZu3t4JES4U@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi", + "gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi" + }, + { + "id": "shipyard-telegram-8", + "description": "Nym Telegram Service Provider", + "address": "8Mqgp12cpF6FSXMeqzxgFgQXvTSapyNqGAi5wy7ub4ge.7z7PDsiJGiGxGz4p77v5L5fZhXBJ5qNZ8CgJwYNr6H6J", + "gateway": "3zd3wrCK8Dz5TXrcvk5dG5s9EEdf4Ck1v9VgBPMMFVkR" + }, + { + "id": "shipyard-telegram-9", + "description": "Nym Telegram Service Provider", + "address": "F3N5eiPDZcGFC985Go4Mpv8p9uxFD1L3jRUdrLCbrZLm.EyTxWwwTwYpPrJBmc97GLd1LpUAphjptS5y1ed182bGk@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ", + "gateway": "GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ" + }, + { + "id": "shipyard-telegram-10", + "description": "Nym Telegram Service Provider", + "address": "G7y7e1nVBr8fmQSzdeAxXnCmmmJb5k8N3E8LBV31KE5g.GRRUCj6t6cCUUjakmTWzidMLiYA7EdCedKnup8osaBC6@AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc", + "gateway": "AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc" + }, + { + "id": "shipyard-telegram-11", + "description": "Nym Telegram Service Provider", + "address": "2kq9Z7RyDZtb8kxXjyP3ZT8VMWHg6JXFDChGuuNBk7Hw.F5XYbBaGSoF8qAo8faPcaNRPHEq3Y25BDcwESeabUS9S@HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM", + "gateway": "HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM" + }, + { + "id": "shipyard-telegram-12", + "description": "Nym Telegram Service Provider", + "address": "GegdtpNzYj4QCgpih9Kxv7ZVZxmVdxYHsDkiPsbT71XG.E8xtE8mrapjzFtyuziZSrsScAKhwZMH5wNpKWtKfzJ5Y@9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J", + "gateway": "9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J" + }, + { + "id": "shipyard-telegram-13", + "description": "Nym Telegram Service Provider", + "address": "4SsrDQeEtG3mpeD9nN5CDdGaCsxFvNeYMhoviDzNNB9f.GyqG6iK5rBvhe3HXLR11m6ULpf13ATgYvkkidLmteDLs@5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS", + "gateway": "5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS" + }, + { + "id": "shipyard-telegram-14", + "description": "Nym Telegram Service Provider", + "address": "9JoHRu2RrSD1fjbj9NSTASgjv9Szep7Nhd9L2PywxbBi.AZhAUDNX6iH8BqXyR5c7TJuzpwMEvDXrabNLGuRukvVf@9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS", + "gateway": "9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS" + }, + { + "id": "shipyard-telegram-15", + "description": "Nym Telegram Service Provider", + "address": "3K174ijjXqCkhMDT9xLcqjS4MXk2QsqZt4PdgNcuUrnn.BNnHnQmWoj6Uo6kkS1QkPqsdHaXrcwyR9F6MnnzDkZJL@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi", + "gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi" + }, + { + "id": "shipyard-telegram-16", + "description": "Nym Telegram Service Provider", + "address": "BqX5Q3MEcbTnM39hUswQchLW68SrqbhL8K5ucrLmtP39.AWrVsFoVC9s6KjdpcasATmZPA3GtMsUxcfHpAkuNdtFG@Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS", + "gateway": "Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS" + }, + { + "id": "shipyard-telegram-17", + "description": "Nym Telegram Service Provider", + "address": "2tQxccgcqdkuUvLqgiEkEN4rNRZ5QknygnKAFcS4gfoe.EVrY5q5sqDqBUbS3wHsRRZhk2MAw1S17hNoH1Bicyv7n@DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom", + "gateway": "DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom" + }, + { + "id": "shipyard-telegram-18", + "description": "Nym Telegram Service Provider", + "address": "8YG1rcEauJA814Nd7hSxjNe2UrRwrGsrXTm1Cmd3gRrU.FxYaYqpNN8PciNsySs3zYPrTB1J8AYUu9DBsM2vVDDfF@7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog", + "gateway": "7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog" + }, + { + "id": "shipyard-telegram-19", + "description": "Nym Telegram Service Provider", + "address": "HPiXADVFLwLQPNpPtyYefzvYntC6tp9UJ5fJZGfkqvDt.2EUUxmeT3AiaUzAcE5SyXRAk8a2JXBkRz4B8McSdkrST@9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD", + "gateway": "9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD" + }, + { + "id": "shipyard-telegram-20", + "description": "Nym Telegram Service Provider", + "address": "2QLnEEnTmf2NRWtcQPWBeRcg7Hej5WSPWRWwtTpEEZWF.BheS78ozc8ngvhsXNNnshdJzpoYsmEvhfn3WKUYF5dRU@C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF", + "gateway": "C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF" + }, + { + "id": "shipyard-telegram-21", + "description": "Nym Telegram Service Provider", + "address": "FuBbnwiANfaXZnn683jBapK5XVm5ttgZSykU3vqPSHoD.94MFGv1VcBLTkRwzBDQUkWjvqtZYVBrJg2Q8JGbizcib@CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK", + "gateway": "CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK" + }, + { + "id": "shipyard-telegram-22", + "description": "Nym Telegram Service Provider", + "address": "9EbQx5jQznSVbftFom7sqUSHAACrsfvMhrzhaFt4A3SZ.D1FQCirL4YKwfcmtMGvB5Rugt5sAzGEhdSjJ3bHVQRZ@7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4", + "gateway": "7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4" + }, + { + "id": "shipyard-telegram-24", + "description": "Nym Telegram Service Provider", + "address": "6Umawwvf551VyB3Ko46NgKLqJdTFJeToCM67mrTmM3G.3A4sesBac4KGuMTFjvYBwLpksMJvbMbteGJQgmm4PV4Y@AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR", + "gateway": "AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR" + }, + { + "id": "shipyard-telegram-25", + "description": "Nym Telegram Service Provider", + "address": "CDtxTeoyqq83JpV9G8cR5HRHRdMMaVspQsCwH3Qnajt3.F5EHK9HFcdGrE2hqA7bK9AUmkbihujYDhtNNqHKxW765@BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH", + "gateway": "BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH" + }, + { + "id": "shipyard-telegram-26", + "description": "Nym Telegram Service Provider", + "address": "HukZkLG2DoarQEqaoDLuqW1GFf2NSHDUMGBZiyJGRYJD.9GyU8wPsyzcvRjcyk8hiNpTJbXCmq5F3VoVhFBZYuHR3@GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN", + "gateway": "GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN" + }, + { + "id": "shipyard-telegram-27", + "description": "Nym Telegram Service Provider", + "address": "773y8iMVJiRk4dRbjQzkJVbrei4TwkePNE5WTEttt77d.3Mw47C9XZj3oAzk1iSqC5Y36tbBsjtaTtdgaHM3Zsdma@7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv", + "gateway": "7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv" + }, + { + "id": "shipyard-telegram-28", + "description": "Nym Telegram Service Provider", + "address": "6jQJEorCu7YiP9HdDaMeHxcNhxeNmZ1kpd836GnqLZX.HsJqEmNTszGecsKqFB37i84nBXxqf4ETgrKmKmBvMGHC@FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE", + "gateway": "FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE" + }, + { + "id": "shipyard-telegram-29", + "description": "Nym Telegram Service Provider", + "address": "BiCSyovpFMuSnTvF2TdiuZwrytXDrd9AH47ZMcCxscVC.G9YpdicA9BBNoVHDgjWjgt17wv5WYKWcbE3vPJJVpSJD@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ", + "gateway":"GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ" + }, + { + "id": "shipyard-telegram-30", + "description": "Nym Telegram Service Provider", + "address": "AQRRAs9oc8QWXAFBs44YhCKUny7AyLsfLy91pwmGgxuf.CWUKoKA1afSKyw5BnFJJg19UDgnaVATupsFhQpyTEBHJ@EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w", + "gateway": "EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w" + }, + { + "id": "shipyard-telegram-31", + "description": "Nym Telegram Service Provider", + "address": "6YqjAZK3Pr1ngiBLcDkotboB5WiN6k6NPpbXvShH4pR5.9Ss6VW3Xbyi8LuxduNNwnXEv9njHCQ2PLSP1UK6tsoa5@42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD", + "gateway": "42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD" + }, + { + "id": "shipyard-telegram-32", + "description": "Nym Telegram Service Provider", + "address": "EmYWLeybmj86Vzr62vxuZ3T15jwMNHggzK7sQwid96yp.GyaF9WprSr56cxUdGf5TpcUvAjb2VbAr8CVBrmBUYAaw@GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC", + "gateway": "GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC" + }, + { + "id": "shipyard-telegram-33", + "description": "Nym Telegram Service Provider", + "address": "4PDb96cck5btTj6G7rsomqwHJsp4qu8uPvFCbwHfjFUx.C5dKbaoakH7egsZvAueRbwLFbmxnQaVMeSr6QTMpuBAA@58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY", + "gateway": "58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY" + }, + { + "id": "shipyard-telegram-34", + "description": "Nym Telegram Service Provider", + "address": "BeZbeMf9vcpUf368qDd85dtLwXLj4Ee5bsHMB2fUD8uX.HELVbppkwU1jmzUAUrCEbHeJfVciSeo8VGAkbJSpwxsb@ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY", + "gateway":"ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY" + }, + { + "id": "shipyard-telegram-35", + "description": "Nym Telegram Service Provider", + "address": "Bp4JRFyf7GB9L9J95AqMPnz9zbGmPnViA5fDXKeNraLJ.D6CTdcjJVxDmH2UQvzXuPWg9Se9xXYe76uDMypXvhzd7@6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N", + "gateway": "6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N" + }, + { + "id": "shipyard-telegram-36", + "description": "Nym Telegram Service Provider", + "address": "91h7io6BGhVjbtC7dbbRcScyTJcTfnMsTQZ6aWMVsrWR.Epb4hANXCp8cGEY3wSgawux991ti9Z5Y1FHTMzAKFa6E@DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe", + "gateway": "DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe" + }, + { + "id": "shipyard-telegram-37", + "description": "Nym Telegram Service Provider", + "address": "Cy2wuwKpWZ3iWzKU3ZWL1qqcVfJ5Cq85dU7UHVWwv2gc.9AhC9b2zVcLnXLGriMdxjpsWJpq6iAdCavDi63udbL89@678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf", + "gateway": "678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf" + }, + { + "id": "shipyard-telegram-38", + "description": "Nym Telegram Service Provider", + "address": "GgUeUWW1NRSuquZYeZf3WkppE92EQUHJrFjNZtYU1jow.CSEjwrRi4f4uyw7N6L2LPKw2tB8spcMbFu99wHZzFZSj@77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm", + "gateway": "77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm" + }, + { + "id": "shipyard-telegram-39", + "description": "Nym Telegram Service Provider", + "address": "kz4zWwSkYiQxqxXFPNcGUByTPQWXascD9RfYsmSxY7n.ajp3SjbBVBjrU9nXpSQXAXzbb6EHJJyhbY6cc1ajayx@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYf", + "gateway": "HyS2UZtZX3kQXdazbdE99DhCjBXjbG61LC9QsmXwbxrU" + } + ] + }, + { + "id": "blockstream", + "description": "Blockstream Green", + "items": [ + { + "id": "nym-blockstream", + "description": "Nym Blockstream Green Service Provider", + "address": "GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW", + "gateway": "2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW" + } + ] + } +]`); +export const Loaded = () => ( + + + +); + +export const ServiceAlreadySelected = () => ( + + console.log('New service provider selected: ', serviceProvider)} + /> + +); diff --git a/nym-connect/mobile/src/components/ServiceSelector.tsx b/nym-connect/mobile/src/components/ServiceSelector.tsx new file mode 100644 index 0000000000..5eb8361d3c --- /dev/null +++ b/nym-connect/mobile/src/components/ServiceSelector.tsx @@ -0,0 +1,179 @@ +import React, { useEffect } from 'react'; +import { + Box, + CircularProgress, + Divider, + FormControl, + InputLabel, + MenuItem, + Select, + Stack, + Typography, +} from '@mui/material'; +import { Service, ServiceProvider, Services } from '../types/directory'; +import { useTauriEvents } from '../utils'; +import { ServiceProviderPopup } from './ServiceProviderPopup'; + +export const ServiceSelector: FCWithChildren<{ + onChange?: (serviceProvider?: ServiceProvider) => void; + services?: Services; + currentSp?: ServiceProvider; +}> = ({ services, currentSp, onChange }) => { + const [service, setService] = React.useState(); + const [serviceProvider, setServiceProvider] = React.useState(currentSp); + const [isPopupVisible, setPopupVisible] = React.useState(false); + + const getService = () => { + if (!services || !currentSp) { + return undefined; + } + return services.find((s) => + s.items.some( + ({ id, address, gateway }) => + id === currentSp.id && address === currentSp.address && gateway === currentSp.gateway, + ), + ); + }; + + useEffect(() => { + if (!service && currentSp) { + setServiceProvider(currentSp); + setService(getService()); + } + }, [currentSp, services]); + + /** + * Gets a random service provider from the currently selected service. + * + * If there is no service selected, or it does not have items, `undefined` is returned. + */ + const getRandomServiceProviderForService = (serviceToUse?: Service): ServiceProvider | undefined => { + if (!serviceToUse?.items.length) { + return undefined; + } + return serviceToUse.items[Math.floor(Math.random() * serviceToUse.items.length)]; + }; + + const handleServiceSelected = React.useCallback( + (newService?: Service) => { + console.log(newService?.id, service?.id); + // if the user has chosen a new service, then pick a random service provider + if (newService?.id !== service?.id) { + const newServiceProvider = getRandomServiceProviderForService(newService); + setServiceProvider(newServiceProvider); + onChange?.(newServiceProvider); + setService(newService); + } + }, + [service], + ); + + // clears the display and fire on change (to trigger upstream storage clearance) + const clearServiceProviderAndFireOnChange = () => { + setService(undefined); + setServiceProvider(undefined); + onChange?.(undefined); + }; + + // when the user clears local storage, reset the selector + useTauriEvents('help://clear-storage', () => { + clearServiceProviderAndFireOnChange(); + }); + + const handleAdvancedSpChange = (newServiceProvider?: ServiceProvider, newService?: Service) => { + setPopupVisible(false); + setService(newService); + setServiceProvider(newServiceProvider); + onChange?.(newServiceProvider); + }; + + const handleNewService = (newServiceId?: string) => { + const newService = (services || []).find((s) => s.id === newServiceId); + setService(newService); + }; + + if (!services) { + return ( + + theme.palette.common.white}> + + Loading services... + + + ); + } + + return ( + + + + Select a service + + + + setPopupVisible(false)} + onServiceProviderChanged={handleAdvancedSpChange} + /> + + ); +}; diff --git a/nym-connect/mobile/src/components/TooltipInfo.tsx b/nym-connect/mobile/src/components/TooltipInfo.tsx new file mode 100644 index 0000000000..678b5337ed --- /dev/null +++ b/nym-connect/mobile/src/components/TooltipInfo.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Divider, Stack, Typography } from '@mui/material'; +import { ServiceProvider } from 'src/types/directory'; + +export const ServiceProviderInfo = ({ serviceProvider }: { serviceProvider: ServiceProvider }) => ( + + + Gateway {serviceProvider.gateway} + + + + Service provider {serviceProvider.address.slice(0, 35)}... + + +); + +export const GatwayWarningInfo = () => ( + + Try disconnecting and connecting again + +); diff --git a/nym-connect/mobile/src/config.ts b/nym-connect/mobile/src/config.ts new file mode 100644 index 0000000000..f405ceac15 --- /dev/null +++ b/nym-connect/mobile/src/config.ts @@ -0,0 +1,3 @@ +export const config = { + DOCS_BASE_URL: 'https://nymtech.net/docs', +}; diff --git a/nym-connect/mobile/src/context/main.tsx b/nym-connect/mobile/src/context/main.tsx new file mode 100644 index 0000000000..a933a8c36f --- /dev/null +++ b/nym-connect/mobile/src/context/main.tsx @@ -0,0 +1,196 @@ +import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { DateTime } from 'luxon'; +import { invoke } from '@tauri-apps/api'; +import { Error } from 'src/types/error'; +import { getVersion } from '@tauri-apps/api/app'; +import { useEvents } from 'src/hooks/events'; +import { UserDefinedGateway } from 'src/types/gateway'; +import { forage } from '@tauri-apps/tauri-forage'; +import { ConnectionStatusKind, GatewayPerformance } from '../types'; +import { ConnectionStatsItem } from '../components/ConnectionStats'; +import { ServiceProvider } from '../types/directory'; + +const FORAGE_KEY = 'nym-connect-user-gateway'; + +type ModeType = 'light' | 'dark'; + +export type TClientContext = { + mode: ModeType; + appVersion?: string; + connectionStatus: ConnectionStatusKind; + connectionStats?: ConnectionStatsItem[]; + connectedSince?: DateTime; + error?: Error; + gatewayPerformance: GatewayPerformance; + selectedProvider?: ServiceProvider; + showInfoModal: boolean; + userDefinedGateway?: UserDefinedGateway; + setMode: (mode: ModeType) => void; + clearError: () => void; + setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void; + setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void; + setConnectedSince: (connectedSince: DateTime | undefined) => void; + setShowInfoModal: (show: boolean) => void; + setRandomSerivceProvider: () => void; + startConnecting: () => Promise; + startDisconnecting: () => Promise; + setUserDefinedGateway: React.Dispatch>; +}; + +export const ClientContext = createContext({} as TClientContext); + +export const ClientContextProvider: FCWithChildren = ({ children }) => { + const [mode, setMode] = useState('dark'); + const [connectionStatus, setConnectionStatus] = useState(ConnectionStatusKind.connected); + const [connectionStats, setConnectionStats] = useState(); + const [connectedSince, setConnectedSince] = useState(); + const [selectedProvider, setSelectedProvider] = React.useState(); + const [serviceProviders, setServiceProviders] = React.useState(); + const [error, setError] = useState(); + const [appVersion, setAppVersion] = useState(); + const [gatewayPerformance, setGatewayPerformance] = useState('Good'); + const [showInfoModal, setShowInfoModal] = useState(false); + const [userDefinedGateway, setUserDefinedGateway] = useState({ isActive: false, gateway: '' }); + + const getAppVersion = async () => { + const version = await getVersion(); + return version; + }; + + const setUserGatewayInStorage = async (gateway: UserDefinedGateway) => { + try { + await forage.setItem({ + key: FORAGE_KEY, + value: gateway, + } as any)(); + } catch (e) { + console.warn(e); + } + return undefined; + }; + + const getUserGatewayFromStorage = async (): Promise => { + try { + const gatewayFromStorage = await forage.getItem({ key: FORAGE_KEY })(); + return gatewayFromStorage; + } catch (e) { + console.warn(e); + } + return undefined; + }; + + const initialiseApp = async () => { + const services = await invoke('get_services'); + const AppVersion = await getAppVersion(); + const storedUserDefinedGateway = await getUserGatewayFromStorage(); + + setAppVersion(AppVersion); + setServiceProviders(services as ServiceProvider[]); + if (storedUserDefinedGateway) setUserDefinedGateway(storedUserDefinedGateway); + }; + + useEvents({ + onError: (e) => setError(e), + onGatewayPerformanceChange: (performance) => setGatewayPerformance(performance), + onStatusChange: (status) => setConnectionStatus(status), + }); + + useEffect(() => { + initialiseApp(); + }, []); + + useEffect(() => { + // when mounting, load the connection state (needed for the Growth window, that checks the connection state) + (async () => { + const currentStatus: ConnectionStatusKind = await invoke('get_connection_status'); + setConnectionStatus(currentStatus); + })(); + }, []); + + const startConnecting = useCallback(async () => { + try { + await invoke('start_connecting'); + } catch (e) { + setError({ title: 'Could not connect', message: e as string }); + console.log(e); + } + }, []); + + const startDisconnecting = useCallback(async () => { + try { + await invoke('start_disconnecting'); + } catch (e) { + console.log(e); + } + }, []); + + const shouldUseUserGateway = !!userDefinedGateway.gateway && userDefinedGateway.isActive; + + const setServiceProvider = async (newServiceProvider: ServiceProvider) => { + await invoke('set_gateway', { + gateway: newServiceProvider.gateway, + }); + await invoke('set_service_provider', { serviceProvider: newServiceProvider.address }); + }; + + const getRandomSPFromList = (services: ServiceProvider[]) => { + const randomSelection = services[Math.floor(Math.random() * services.length)]; + + if (shouldUseUserGateway) return { ...randomSelection, gateway: userDefinedGateway.gateway } as ServiceProvider; + return randomSelection; + }; + + const setRandomSerivceProvider = async () => { + if (serviceProviders) { + const randomServiceProvider = getRandomSPFromList(serviceProviders); + await setServiceProvider(randomServiceProvider); + await setUserGatewayInStorage(userDefinedGateway); + setSelectedProvider(randomServiceProvider); + } + return undefined; + }; + + const clearError = () => setError(undefined); + + const contextValue = useMemo( + () => ({ + mode, + appVersion, + setMode, + error, + clearError, + connectionStatus, + setConnectionStatus, + connectionStats, + showInfoModal, + setConnectionStats, + selectedProvider, + connectedSince, + setConnectedSince, + setRandomSerivceProvider, + startConnecting, + startDisconnecting, + gatewayPerformance, + setShowInfoModal, + userDefinedGateway, + setUserDefinedGateway, + }), + [ + mode, + appVersion, + error, + showInfoModal, + connectedSince, + connectionStatus, + connectionStats, + connectedSince, + gatewayPerformance, + selectedProvider, + userDefinedGateway, + ], + ); + + return {children}; +}; + +export const useClientContext = () => useContext(ClientContext); diff --git a/nym-connect/mobile/src/context/mocks/main.tsx b/nym-connect/mobile/src/context/mocks/main.tsx new file mode 100644 index 0000000000..6d0f7d9a82 --- /dev/null +++ b/nym-connect/mobile/src/context/mocks/main.tsx @@ -0,0 +1,31 @@ +import React, { useMemo } from 'react'; +import { ConnectionStatusKind } from 'src/types'; +import { ClientContext, TClientContext } from '../main'; + +const mockValues: TClientContext = { + appVersion: 'v1.x.x', + mode: 'dark', + connectionStatus: ConnectionStatusKind.disconnected, + selectedProvider: { id: '1', description: 'Keybase service provider', gateway: 'abc123', address: '123abc' }, + gatewayPerformance: 'Good', + showInfoModal: false, + userDefinedGateway: { isActive: false, gateway: '' }, + setShowInfoModal: () => {}, + setMode: () => {}, + clearError: () => {}, + setConnectedSince: () => {}, + setConnectionStats: () => {}, + setConnectionStatus: () => {}, + startConnecting: async () => {}, + startDisconnecting: async () => {}, + setRandomSerivceProvider: () => {}, + setUserDefinedGateway: () => {}, +}; + +export const MockProvider: FCWithChildren<{ + children?: React.ReactNode; + connectionStatus?: ConnectionStatusKind; +}> = ({ connectionStatus = ConnectionStatusKind.disconnected, children }) => { + const value = useMemo(() => ({ ...mockValues, connectionStatus }), [connectionStatus]); + return {children}; +}; diff --git a/nym-connect/mobile/src/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf b/nym-connect/mobile/src/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000000..0fea34ba24 Binary files /dev/null and b/nym-connect/mobile/src/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf differ diff --git a/nym-connect/mobile/src/fonts/OpenSans-VariableFont_wdth,wght.ttf b/nym-connect/mobile/src/fonts/OpenSans-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000000..51dd3c3be2 Binary files /dev/null and b/nym-connect/mobile/src/fonts/OpenSans-VariableFont_wdth,wght.ttf differ diff --git a/nym-connect/mobile/src/fonts/fonts.css b/nym-connect/mobile/src/fonts/fonts.css new file mode 100644 index 0000000000..bf875e952d --- /dev/null +++ b/nym-connect/mobile/src/fonts/fonts.css @@ -0,0 +1,6 @@ +@font-face { + font-family: 'Open Sans'; + src: url('./OpenSans-VariableFont_wdth,wght.ttf') format('truetype-variations'), + url('./OpenSans-Italic-VariableFont_wdth,wght.ttf') format('truetype-variations'); + font-weight: 100 1000; +} \ No newline at end of file diff --git a/nym-connect/mobile/src/hooks/events.ts b/nym-connect/mobile/src/hooks/events.ts new file mode 100644 index 0000000000..4b2c4fcc55 --- /dev/null +++ b/nym-connect/mobile/src/hooks/events.ts @@ -0,0 +1,68 @@ +import { useEffect, useRef } from 'react'; +import { listen, UnlistenFn } from '@tauri-apps/api/event'; +import { ConnectionStatusKind, GatewayPerformance } from 'src/types'; +import { Error } from 'src/types/error'; +import { TauriEvent } from 'src/types/event'; + +const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed'; + +export const useEvents = ({ + onError, + onStatusChange, + onGatewayPerformanceChange, +}: { + onError: (error: Error) => void; + onStatusChange: (status: ConnectionStatusKind) => void; + onGatewayPerformanceChange: (status: GatewayPerformance) => void; +}) => { + const timerId = useRef(); + + useEffect(() => { + const unlisten: UnlistenFn[] = []; + + // TODO: fix typings + listen(TAURI_EVENT_STATUS_CHANGED, (event) => { + const { status } = event.payload as any; + console.log(TAURI_EVENT_STATUS_CHANGED, { status, event }); + onStatusChange(status); + }) + .then((result) => { + unlisten.push(result); + }) + .catch((e) => console.log(e)); + + listen('socks5-event', (e: TauriEvent) => { + console.log(e); + onError(e.payload); + }).then((result) => { + unlisten.push(result); + }); + + listen('socks5-status-event', (e: TauriEvent) => { + if (e.payload.message.includes('slow')) { + onGatewayPerformanceChange('Poor'); + + if (timerId?.current) { + clearTimeout(timerId.current); + } + + timerId.current = setTimeout(() => { + onGatewayPerformanceChange('Good'); + }, 10000); + } + }).then((result) => { + unlisten.push(result); + }); + + listen('socks5-connection-fail-event', (e: TauriEvent) => { + onError({ title: 'Connection failed', message: `${e.payload.message} - Please disconnect and reconnect.` }); + onGatewayPerformanceChange('Poor'); + }).then((result) => { + unlisten.push(result); + }); + + return () => { + unlisten.forEach((unsubscribe) => unsubscribe()); + }; + }, []); +}; diff --git a/nym-connect/mobile/src/index.tsx b/nym-connect/mobile/src/index.tsx new file mode 100644 index 0000000000..996825445d --- /dev/null +++ b/nym-connect/mobile/src/index.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { ErrorBoundary } from 'react-error-boundary'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { GlobalStyles } from '@mui/material'; +import { ClientContextProvider } from './context/main'; +import { ErrorFallback } from './components/Error'; +import { NymMixnetTheme } from './theme'; +import { AppWindowFrame } from './components/AppWindowFrame'; +import { AppRoutes } from './routes'; + +const elem = document.getElementById('root'); + +if (elem) { + const root = createRoot(elem); + root.render( + + + + + + + + + + + + , + ); +} diff --git a/nym-connect/mobile/src/layouts/ConnectionLayout.tsx b/nym-connect/mobile/src/layouts/ConnectionLayout.tsx new file mode 100644 index 0000000000..300a971e15 --- /dev/null +++ b/nym-connect/mobile/src/layouts/ConnectionLayout.tsx @@ -0,0 +1,24 @@ +import { Box, Stack } from '@mui/material'; +import React from 'react'; + +export const ConnectionLayout = ({ + TopContent, + ConnectButton, + BottomContent, +}: { + TopContent: React.ReactNode; + ConnectButton: React.ReactNode; + BottomContent: React.ReactNode; +}) => ( + + + {TopContent} + + + {ConnectButton} + + + {BottomContent} + + +); diff --git a/nym-connect/mobile/src/pages/connection/Connected.tsx b/nym-connect/mobile/src/pages/connection/Connected.tsx new file mode 100644 index 0000000000..d1f59e55eb --- /dev/null +++ b/nym-connect/mobile/src/pages/connection/Connected.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { Box, Stack } from '@mui/material'; +import { DateTime } from 'luxon'; +import { IpAddressAndPortModal } from 'src/components/IpAddressAndPortModal'; +import { ConnectionTimer } from 'src/components/ConntectionTimer'; +import { ConnectionStatus } from 'src/components/ConnectionStatus'; +import { ConnectionStatusKind, GatewayPerformance } from 'src/types'; +import { ConnectionStatsItem } from 'src/components/ConnectionStats'; +import { IpAddressAndPort } from 'src/components/IpAddressAndPort'; +import { ServiceProvider } from 'src/types/directory'; +import { ExperimentalWarning } from 'src/components/ExperimentalWarning'; +import { ConnectionLayout } from 'src/layouts/ConnectionLayout'; +import { PowerButton } from 'src/components/PowerButton/PowerButton'; +import { Error } from 'src/types/error'; +import { InfoModal } from 'src/components/InfoModal'; + +export const Connected: FCWithChildren<{ + error?: Error; + status: ConnectionStatusKind; + showInfoModal: boolean; + gatewayPerformance: GatewayPerformance; + stats: ConnectionStatsItem[]; + ipAddress: string; + port: number; + connectedSince?: DateTime; + busy?: boolean; + isError?: boolean; + serviceProvider?: ServiceProvider; + clearError: () => void; + onConnectClick: (status: ConnectionStatusKind) => void; + closeInfoModal: () => void; +}> = ({ + error, + status, + showInfoModal, + gatewayPerformance, + ipAddress, + port, + connectedSince, + busy, + isError, + serviceProvider, + clearError, + onConnectClick, + closeInfoModal, +}) => ( + <> + {error && } + + + + +
+ } + ConnectButton={ + + } + BottomContent={ + + + + + + + + + } + /> + +); diff --git a/nym-connect/mobile/src/pages/connection/Disconnected.tsx b/nym-connect/mobile/src/pages/connection/Disconnected.tsx new file mode 100644 index 0000000000..d3937fea24 --- /dev/null +++ b/nym-connect/mobile/src/pages/connection/Disconnected.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Stack, Typography } from '@mui/material'; +import { ConnectionStatus } from 'src/components/ConnectionStatus'; +import { ConnectionTimer } from 'src/components/ConntectionTimer'; +import { InfoModal } from 'src/components/InfoModal'; +import { Error } from 'src/types/error'; +import { ExperimentalWarning } from 'src/components/ExperimentalWarning'; +import { ServiceProvider, Services } from 'src/types/directory'; +import { ConnectionStatusKind } from 'src/types'; +import { PowerButton } from 'src/components/PowerButton/PowerButton'; +import { Box } from '@mui/system'; +import { ConnectionLayout } from 'src/layouts/ConnectionLayout'; + +export const Disconnected: FCWithChildren<{ + error?: Error; + status: ConnectionStatusKind; + services?: Services; + busy?: boolean; + isError?: boolean; + serviceProvider?: ServiceProvider; + clearError: () => void; + onConnectClick: (status: ConnectionStatusKind) => void; +}> = ({ status, error, onConnectClick, clearError }) => ( + <> + {error && } + + + + + } + ConnectButton={} + BottomContent={ + + + + You are not protected + + + + + + + } + /> + +); diff --git a/nym-connect/mobile/src/pages/connection/index.tsx b/nym-connect/mobile/src/pages/connection/index.tsx new file mode 100644 index 0000000000..c13eb9e723 --- /dev/null +++ b/nym-connect/mobile/src/pages/connection/index.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { Stack } from '@mui/material'; +import { forage } from '@tauri-apps/tauri-forage'; +import { DateTime } from 'luxon'; +import { useClientContext } from 'src/context/main'; +import { useTauriEvents } from 'src/utils'; +import { Connected } from './Connected'; +import { Disconnected } from './Disconnected'; + +export const ConnectionPage = () => { + const context = useClientContext(); + const [busy, setBusy] = React.useState(); + + useTauriEvents('help://clear-storage', (_event) => { + console.log('About to clear local storage...'); + // clear local storage + try { + forage.clear()(); + console.log('Local storage cleared'); + } catch (e) { + console.error('Failed to clear local storage', e); + } + }); + + const handleConnectClick = async () => { + const currentStatus = context.connectionStatus; + if (currentStatus === 'connected' || currentStatus === 'disconnected') { + setBusy(true); + // eslint-disable-next-line default-case + switch (currentStatus) { + case 'disconnected': + await context.setRandomSerivceProvider(); + await context.startConnecting(); + context.setConnectedSince(DateTime.now()); + context.setShowInfoModal(true); + break; + case 'connected': + await context.startDisconnecting(); + context.setConnectedSince(undefined); + break; + } + setBusy(false); + } + }; + + const closeInfoModal = () => context.setShowInfoModal(false); + + if (context.connectionStatus === 'connected') + return ( + + + + ); + + return ( + + + + ); +}; diff --git a/nym-connect/mobile/src/pages/menu/Apps.tsx b/nym-connect/mobile/src/pages/menu/Apps.tsx new file mode 100644 index 0000000000..d75a7a5536 --- /dev/null +++ b/nym-connect/mobile/src/pages/menu/Apps.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Divider, Typography } from '@mui/material'; +import { Box } from '@mui/system'; + +const appsSchema = { + messagingApps: ['Telegram', 'Keybase'], + wallets: ['Blockstream', 'Electrum'], +}; + +export const CompatibleApps = () => ( + + + Supported apps + + + Messaging apps + + + + {appsSchema.messagingApps.map((app) => ( + + {app} + + ))} + + + + Wallets + + + + {appsSchema.wallets.map((wallet) => ( + + {wallet} + + ))} + + +); diff --git a/nym-connect/mobile/src/pages/menu/Guide.tsx b/nym-connect/mobile/src/pages/menu/Guide.tsx new file mode 100644 index 0000000000..2323d68a82 --- /dev/null +++ b/nym-connect/mobile/src/pages/menu/Guide.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import { HelpPage } from 'src/components/HelpPage'; +import Image1 from '../../assets/help-step-one.png'; +import Image2 from '../../assets/help-step-two.png'; +import Image4 from '../../assets/help-step-four.png'; + +export const HelpGuide = () => { + const [step, setStep] = useState(1); + const TOTAL_STEPS = 3; + + if (step === 1) + return ( + setStep(2)} + /> + ); + + if (step === 2) + return ( + setStep(1)} + onNext={() => setStep(3)} + /> + ); + + if (step === 3) + return ( + setStep(2)} + /> + ); + + return null; +}; diff --git a/nym-connect/mobile/src/pages/menu/Settings.tsx b/nym-connect/mobile/src/pages/menu/Settings.tsx new file mode 100644 index 0000000000..f200c2c605 --- /dev/null +++ b/nym-connect/mobile/src/pages/menu/Settings.tsx @@ -0,0 +1,80 @@ +import React, { ChangeEvent, useState } from 'react'; +import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; +import { Box, FormControl, FormControlLabel, FormHelperText, Stack, Switch, Typography } from '@mui/material'; +import { useClientContext } from 'src/context/main'; +import { ConnectionStatusKind } from 'src/types'; +import { AppVersion } from 'src/components/AppVersion'; + +export const Settings = () => { + const { userDefinedGateway, setUserDefinedGateway } = useClientContext(); + const [gatewayKey, setGatewayKey] = useState(userDefinedGateway?.gateway); + + const handleIsValidGatewayKey = (isValid: boolean) => { + let gateway: string | undefined; + + if (isValid) { + gateway = gatewayKey; + } + + setUserDefinedGateway((current) => ({ ...current, gateway })); + }; + + const handleChange = (e: ChangeEvent) => { + console.warn('HANERE***'); + setUserDefinedGateway((current) => ({ ...current, isActive: e.target.checked })); + }; + + const { connectionStatus } = useClientContext(); + + return ( + + + + + Select your Gateway + + + Use a gateway of your choice + + + + } + label={userDefinedGateway?.isActive ? 'On' : 'Off'} + /> + {connectionStatus === ConnectionStatusKind.connected && ( + This setting is disabled during an active connection + )} + {userDefinedGateway?.isActive && ( + + )} + + + + + To find a gateway go to{' '} + + explorer.nymtech.net/network-components/gateways + + + + + + + ); +}; diff --git a/nym-connect/mobile/src/pages/menu/index.tsx b/nym-connect/mobile/src/pages/menu/index.tsx new file mode 100644 index 0000000000..47840d6668 --- /dev/null +++ b/nym-connect/mobile/src/pages/menu/index.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Apps, HelpOutline, Settings } from '@mui/icons-material'; +import { Stack, Link, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography } from '@mui/material'; +import { Link as RouterLink } from 'react-router-dom'; +import { AppVersion } from 'src/components/AppVersion'; + +const menuSchema = [ + { title: 'Supported apps', icon: Apps, path: 'apps' }, + { title: 'How to connect guide', icon: HelpOutline, path: 'guide' }, + { title: 'Settings', icon: Settings, path: 'settings' }, +]; + +export const Menu = () => ( + + + {menuSchema.map((item) => ( + + + + + + {' '} + + + {item.title} + + + + + + ))} + + + +); diff --git a/nym-connect/mobile/src/routes/index.tsx b/nym-connect/mobile/src/routes/index.tsx new file mode 100644 index 0000000000..ec24080c6c --- /dev/null +++ b/nym-connect/mobile/src/routes/index.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Routes, Route } from 'react-router-dom'; +import { ConnectionPage } from 'src/pages/connection'; +import { Menu } from 'src/pages/menu'; +import { CompatibleApps } from 'src/pages/menu/Apps'; +import { HelpGuide } from 'src/pages/menu/Guide'; +import { Settings } from 'src/pages/menu/Settings'; + +export const AppRoutes = () => ( + + } /> + + } /> + } /> + } /> + } /> + + +); diff --git a/nym-connect/mobile/src/stories/AppFlow.stories.tsx b/nym-connect/mobile/src/stories/AppFlow.stories.tsx new file mode 100644 index 0000000000..c00cb2aa9e --- /dev/null +++ b/nym-connect/mobile/src/stories/AppFlow.stories.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Box } from '@mui/material'; +import { DateTime } from 'luxon'; +import { Disconnected } from 'src/pages/connection/Disconnected'; +import { Connected } from 'src/pages/connection/Connected'; +import { ConnectionStatusKind } from 'src/types'; +import { AppWindowFrame } from '../components/AppWindowFrame'; +import { useClientContext } from '../context/main'; +import { Services } from '../types/directory'; + +export default { + title: 'App/Flow', + component: AppWindowFrame, +} as ComponentMeta; + +const width = 240; +const height = 575; + +export const Mock: ComponentStory = () => { + const context = useClientContext(); + const [busy, setBusy] = React.useState(); + const services: Services = [ + { + id: 'keybase', + description: 'Keybase', + items: [ + { + id: 'nym-keybase', + description: 'Nym Keybase Service Provider', + address: '1234.5678', + gateway: 'abcedf', + }, + ], + }, + ]; + const handleConnectClick = React.useCallback(() => { + const oldStatus = context.connectionStatus; + if (oldStatus === 'connected' || oldStatus === 'disconnected') { + setBusy(true); + + // eslint-disable-next-line default-case + switch (oldStatus) { + case 'disconnected': + context.setConnectionStatus(ConnectionStatusKind.connecting); + break; + case 'connected': + context.setConnectionStatus(ConnectionStatusKind.disconnecting); + break; + } + + setTimeout(() => { + // eslint-disable-next-line default-case + switch (oldStatus) { + case 'disconnected': + context.setConnectedSince(DateTime.now()); + context.setConnectionStatus(ConnectionStatusKind.connected); + break; + case 'connected': + context.setConnectionStatus(ConnectionStatusKind.disconnected); + break; + } + setBusy(false); + }, 5000); + } + }, [context.connectionStatus]); + + if ( + context.connectionStatus === ConnectionStatusKind.disconnected || + context.connectionStatus === ConnectionStatusKind.connecting + ) { + return ( + + + {}} + /> + + + ); + } + + return ( + + {}} + gatewayPerformance="Good" + showInfoModal={false} + closeInfoModal={() => undefined} + status={context.connectionStatus} + busy={busy} + onConnectClick={handleConnectClick} + ipAddress="127.0.0.1" + port={1080} + connectedSince={context.connectedSince} + serviceProvider={services[0].items[0]} + stats={[ + { + label: 'in:', + totalBytes: 1024, + rateBytesPerSecond: 1024 * 1024 * 1024 + 10, + }, + { + label: 'out:', + totalBytes: 1024 * 1024 * 1024 * 1024 * 20, + rateBytesPerSecond: 1024 * 1024 + 10, + }, + ]} + /> + + ); +}; diff --git a/nym-connect/mobile/src/stories/AppWindowFrame.stories.tsx b/nym-connect/mobile/src/stories/AppWindowFrame.stories.tsx new file mode 100644 index 0000000000..e8fa9a8b6b --- /dev/null +++ b/nym-connect/mobile/src/stories/AppWindowFrame.stories.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Box } from '@mui/material'; +import { AppWindowFrame } from '../components/AppWindowFrame'; + +export default { + title: 'App/AppWindowFrame', + component: AppWindowFrame, +} as ComponentMeta; + +export const Default: ComponentStory = () => ( + + + Culpa deserunt cupidatat culpa nisi aute dolore nisi deserunt cillum consequat elit. Nostrud id occaecat + consectetur consectetur excepteur labore consectetur. Laboris tempor consequat qui exercitation adipisicing sunt + cupidatat est. Officia dolore qui eu dolor velit ex ea qui laborum. Mollit ut est sit irure elit ad ut deserunt. + + +); diff --git a/nym-connect/mobile/src/stories/ConnectedLayout.stories.tsx b/nym-connect/mobile/src/stories/ConnectedLayout.stories.tsx new file mode 100644 index 0000000000..426252dc63 --- /dev/null +++ b/nym-connect/mobile/src/stories/ConnectedLayout.stories.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Box } from '@mui/material'; +import { DateTime } from 'luxon'; +import { Connected } from 'src/pages/connection/Connected'; +import { ConnectionStatusKind } from 'src/types'; + +const onClick = () => undefined; + +export default { + title: 'Layouts/ConnectedLayout', + component: Connected, +} as ComponentMeta; + +export const Default: ComponentStory = () => ( + + {}} + gatewayPerformance="Good" + showInfoModal={false} + closeInfoModal={() => undefined} + status={ConnectionStatusKind.connected} + connectedSince={DateTime.now()} + ipAddress="127.0.0.1" + serviceProvider={{ id: 'service 1', description: 'good services', address: 'abc123', gateway: '8910xyz' }} + port={1080} + stats={[ + { + label: 'in:', + totalBytes: 1024, + rateBytesPerSecond: 1024 * 1024 * 1024 + 10, + }, + { + label: 'out:', + totalBytes: 1024 * 1024 * 1024 * 1024 * 20, + rateBytesPerSecond: 1024 * 1024 + 10, + }, + ]} + onConnectClick={onClick} + /> + +); diff --git a/nym-connect/mobile/src/stories/ConnectionButton.stories.tsx b/nym-connect/mobile/src/stories/ConnectionButton.stories.tsx new file mode 100644 index 0000000000..6fcf7530ad --- /dev/null +++ b/nym-connect/mobile/src/stories/ConnectionButton.stories.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { ConnectionStatusKind } from 'src/types'; +import { ConnectionButton } from '../components/ConnectionButton'; + +export default { + title: 'Components/ConnectionButton', + component: ConnectionButton, +} as ComponentMeta; + +export const Disconnected: ComponentStory = () => ( + +); + +export const Connecting: ComponentStory = () => ( + +); + +export const Connected: ComponentStory = () => ( + +); + +export const Disconnecting: ComponentStory = () => ( + +); diff --git a/nym-connect/mobile/src/stories/ConnectionStats.stories.tsx b/nym-connect/mobile/src/stories/ConnectionStats.stories.tsx new file mode 100644 index 0000000000..7faea66c16 --- /dev/null +++ b/nym-connect/mobile/src/stories/ConnectionStats.stories.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { ConnectionStats } from '../components/ConnectionStats'; + +export default { + title: 'Components/ConnectionStats', + component: ConnectionStats, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +// 👇 Each story then reuses that template +export const Default = Template.bind({}); +Default.args = { + stats: [ + { + label: 'in:', + totalBytes: 1024, + rateBytesPerSecond: 1024 * 1024 * 1024 + 10, + }, + { + label: 'out:', + totalBytes: 1024 * 1024 * 1024 * 1024 * 20, + rateBytesPerSecond: 1024 * 1024 + 10, + }, + ], +}; diff --git a/nym-connect/mobile/src/stories/ConnectionStatus.stories.tsx b/nym-connect/mobile/src/stories/ConnectionStatus.stories.tsx new file mode 100644 index 0000000000..d98e262374 --- /dev/null +++ b/nym-connect/mobile/src/stories/ConnectionStatus.stories.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { DateTime } from 'luxon'; +import { ConnectionStatus } from '../components/ConnectionStatus'; +import { ConnectionStatusKind } from '../types'; + +export default { + title: 'Components/ConnectionStatus', + component: ConnectionStatus, +} as ComponentMeta; + +export const Disconnected: ComponentStory = () => ( + +); + +export const Connecting: ComponentStory = () => ( + +); + +export const Connected: ComponentStory = () => ( + +); + +export const Disconnecting: ComponentStory = () => ( + +); diff --git a/nym-connect/mobile/src/stories/DefaultLayout.stories.tsx b/nym-connect/mobile/src/stories/DefaultLayout.stories.tsx new file mode 100644 index 0000000000..4d1b869a10 --- /dev/null +++ b/nym-connect/mobile/src/stories/DefaultLayout.stories.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Box } from '@mui/material'; +import { Disconnected } from 'src/pages/connection/Disconnected'; +import { ConnectionStatusKind } from '../types'; + +export default { + title: 'Layouts/DefaultLayout', + component: Disconnected, +} as ComponentMeta; + +const onClick = () => undefined; + +export const Default: ComponentStory = () => ( + + {}} + error={undefined} + onConnectClick={onClick} + /> + +); + +export const WithServices: ComponentStory = () => ( + + {}} + error={undefined} + onConnectClick={onClick} + /> + +); diff --git a/nym-connect/mobile/src/stories/Introduction.stories.mdx b/nym-connect/mobile/src/stories/Introduction.stories.mdx new file mode 100644 index 0000000000..eb1d51fc24 --- /dev/null +++ b/nym-connect/mobile/src/stories/Introduction.stories.mdx @@ -0,0 +1,7 @@ +import { Meta } from '@storybook/addon-docs'; + + + +# nym-connect + +Components for connecting to the Nym mixnet. diff --git a/nym-connect/mobile/src/stories/IpAddressAndPort.stories.tsx b/nym-connect/mobile/src/stories/IpAddressAndPort.stories.tsx new file mode 100644 index 0000000000..943b3a40b4 --- /dev/null +++ b/nym-connect/mobile/src/stories/IpAddressAndPort.stories.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IpAddressAndPort } from '../components/IpAddressAndPort'; + +export default { + title: 'Components/IpAddressAndPort', + component: IpAddressAndPort, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +// 👇 Each story then reuses that template +export const Default = Template.bind({}); +Default.args = { label: 'Socks5 address', ipAddress: '127.0.0.1', port: 1080 }; diff --git a/nym-connect/mobile/src/stories/PowerButton.stories.tsx b/nym-connect/mobile/src/stories/PowerButton.stories.tsx new file mode 100644 index 0000000000..6cb2896849 --- /dev/null +++ b/nym-connect/mobile/src/stories/PowerButton.stories.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { PowerButton } from 'src/components/PowerButton/PowerButton'; +import { ConnectionStatusKind } from 'src/types'; + +export default { + title: 'Components/PowerButton', + component: PowerButton, +} as ComponentMeta; + +export const Disconnected: ComponentStory = () => ( + +); + +export const Connecting: ComponentStory = () => ( + +); + +export const Connected: ComponentStory = () => ( + +); + +export const Disconnecting: ComponentStory = () => ( + +); + +export const Disabled: ComponentStory = () => ( + +); diff --git a/nym-connect/mobile/src/theme/index.tsx b/nym-connect/mobile/src/theme/index.tsx new file mode 100644 index 0000000000..d307fe46ee --- /dev/null +++ b/nym-connect/mobile/src/theme/index.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { createTheme, ThemeProvider } from '@mui/material/styles'; +import { CssBaseline } from '@mui/material'; +import { getDesignTokens } from './theme'; +// eslint-disable-next-line import/no-relative-packages +import '../../../../assets/fonts/non-variable/fonts.css'; + +/** + * Provides the theme for Nym Connect by reacting to the light/dark mode choice stored in the app context. + */ +export const NymMixnetTheme: FCWithChildren<{ mode: 'light' | 'dark' }> = ({ children, mode }) => { + const theme = React.useMemo(() => createTheme(getDesignTokens(mode)), [mode]); + return ( + + + {children} + + ); +}; + +export const NymShipyardTheme: FCWithChildren<{ mode?: 'light' | 'dark' }> = ({ children, mode = 'dark' }) => { + const theme = React.useMemo(() => createTheme(getDesignTokens(mode, true)), [mode]); + return ( + + + {children} + + ); +}; diff --git a/nym-connect/mobile/src/theme/mui-theme.d.ts b/nym-connect/mobile/src/theme/mui-theme.d.ts new file mode 100644 index 0000000000..76db4b1a99 --- /dev/null +++ b/nym-connect/mobile/src/theme/mui-theme.d.ts @@ -0,0 +1,90 @@ +/* eslint-disable no-shadow,@typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-interface */ +import { Theme, ThemeOptions, Palette, PaletteOptions } from '@mui/material/styles'; +import { PaletteMode } from '@mui/material'; + +/** + * If you are unfamiliar with Material UI theming, please read the following first: + * - https://mui.com/customization/theming/ + * - https://mui.com/customization/palette/ + * - https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette + * + * This file adds typings to the theme using Typescript's module augmentation. + * + * Read the following if you are unfamiliar with module augmentation and declaration merging. Then + * look at the recommendations from Material UI docs for implementation: + * - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation + * - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces + * - https://mui.com/customization/palette/#adding-new-colors + * + * + * IMPORTANT: + * + * The type augmentation must match MUI's definitions. So, notice the use of `interface` rather than + * `type Foo = { ... }` - this is necessary to merge the definitions. + */ + +declare module '@mui/material/styles' { + /** + * This interface defines a palette used across Nym for branding + */ + interface NymPalette { + highlight: string; + cta: string; + success: string; + warning: string; + info: string; + fee: string; + background: { light: string; dark: string }; + text: { + light: string; + dark: string; + }; + shipyard: string; + } + + interface NymPaletteVariant { + mode: PaletteMode; + background: { + main: string; + paper: string; + }; + text: { + main: string; + }; + topNav: { + background: string; + }; + shipyard: { + main: string; + }; + } + + /** + * A palette definition only for the Nym Mixnet that extends the Nym palette + */ + interface NymMixnetPalette { + nymMixnet: {}; + } + + interface NymPaletteAndNymMixnetPalette { + nym: NymPalette & NymMixnetPalette; + } + + type NymPaletteAndNymMixnetPaletteOptions = Partial; + + /** + * Add anything not palette related to the theme here + */ + interface NymTheme {} + + /** + * This augments the definitions of the MUI Theme with the Nym theme, as well as + * a partial `ThemeOptions` type used by `createTheme` + * + * IMPORTANT: only add extensions to the interfaces above, do not modify the lines below + */ + interface Theme extends NymTheme {} + interface ThemeOptions extends Partial {} + interface Palette extends NymPaletteAndNymMixnetPalette {} + interface PaletteOptions extends NymPaletteAndNymMixnetPaletteOptions {} +} diff --git a/nym-connect/mobile/src/theme/theme.tsx b/nym-connect/mobile/src/theme/theme.tsx new file mode 100644 index 0000000000..23c8ff8b57 --- /dev/null +++ b/nym-connect/mobile/src/theme/theme.tsx @@ -0,0 +1,284 @@ +import { PaletteMode } from '@mui/material'; +import { + createTheme, + NymMixnetPalette, + NymPalette, + NymPaletteVariant, + PaletteOptions, + ThemeOptions, +} from '@mui/material/styles'; + +//----------------------------------------------------------------------------------------------- +// Nym palette type definitions +// + +/** + * The Nym palette. + * + * IMPORTANT: do not export this constant, always use the MUI `useTheme` hook to get the correct + * colours for dark/light mode. + */ +const nymPalette: NymPalette = { + /** emphasises important elements */ + highlight: '#21D072', + cta: '#FB6E4E', + success: '#21D073', + info: '#60D7EF', + warning: '#FFE600', + fee: '#967FF0', + background: { light: '#F4F6F8', dark: '#1D2125' }, + text: { + light: '#F2F2F2', + dark: '#1D2125', + }, + shipyard: '#817FFA', +}; + +const darkMode: NymPaletteVariant = { + mode: 'dark', + background: { + main: '#1D2125', + paper: '#242C3D', + }, + text: { + main: '#F2F2F2', + }, + topNav: { + background: '#111826', + }, + shipyard: { + main: '#817FFA', + }, +}; + +const lightMode: NymPaletteVariant = { + mode: 'light', + background: { + main: '#F2F2F2', + paper: '#FFFFFF', + }, + text: { + main: '#1D2125', + }, + topNav: { + background: '#111826', + }, + shipyard: { + main: '#817FFA', + }, +}; + +/** + * Nym palette specific to the Nym Mixnode + * + * IMPORTANT: do not export this constant, always use the MUI `useTheme` hook to get the correct + * colours for dark/light mode. + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const nymMixnetPalette = (variant: NymPaletteVariant): NymMixnetPalette => ({ + nymMixnet: {}, +}); + +//----------------------------------------------------------------------------------------------- +// Nym palettes for light and dark mode +// + +/** + * Map a Nym palette variant onto the MUI palette + */ +const variantToMUIPalette = (variant: NymPaletteVariant): PaletteOptions => ({ + text: { + primary: variant.text.main, + }, + primary: { + main: nymPalette.highlight, + contrastText: '#fff', + }, + secondary: { + main: variant.mode === 'dark' ? nymPalette.background.light : nymPalette.background.dark, + }, + success: { + main: nymPalette.success, + }, + info: { + main: nymPalette.info, + }, + warning: { + main: nymPalette.warning, + }, + background: { + default: variant.background.main, + paper: variant.background.paper, + }, +}); + +/** + * Map a Nym palette variant onto the MUI palette for Shipyard + */ +const variantShipyardToMUIPalette = (variant: NymPaletteVariant): PaletteOptions => ({ + text: { + primary: variant.text.main, + }, + primary: { + main: nymPalette.shipyard, + contrastText: '#fff', + }, + secondary: { + main: variant.mode === 'dark' ? nymPalette.background.light : nymPalette.background.dark, + }, + success: { + main: nymPalette.success, + }, + info: { + main: nymPalette.info, + }, + warning: { + main: nymPalette.warning, + }, + background: { + default: variant.background.main, + paper: variant.background.paper, + }, +}); + +/** + * Returns the Network Explorer palette for light mode. + */ +const createLightModePalette = (): PaletteOptions => ({ + nym: { + ...nymPalette, + ...nymMixnetPalette(lightMode), + }, + ...variantToMUIPalette(lightMode), +}); + +/** + * Returns the Network Explorer palette for dark mode. + */ +const createDarkModePalette = (): PaletteOptions => ({ + nym: { + ...nymPalette, + ...nymMixnetPalette(darkMode), + }, + ...variantToMUIPalette(darkMode), +}); + +/** + * Returns the Shipyard palette for dark mode. + */ +const createShipyardDarkModePalette = (): PaletteOptions => ({ + nym: { + ...nymPalette, + ...nymMixnetPalette(darkMode), + }, + ...variantShipyardToMUIPalette(darkMode), +}); + +/** + * IMPORANT: if you need to get the default MUI theme, use the following + * + * import { createTheme as systemCreateTheme } from '@mui/system'; + * + * // get the MUI system defaults for light mode + * const systemTheme = systemCreateTheme({ palette: { mode: 'light' } }); + * + * + * return { + * // change `primary` to default MUI `success` + * primary: { + * main: systemTheme.palette.success.main, + * }, + * nym: { + * ...nymPalette, + * ...nymMixnetPalette, + * }, + * }; + */ + +//----------------------------------------------------------------------------------------------- +// Nym theme overrides +// + +/** + * Gets the theme options to be passed to `createTheme`. + * + * Based on pattern from https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette. + * + * @param mode The theme mode: 'light' or 'dark' + */ +export const getDesignTokens = (mode: PaletteMode, isShipyard: boolean = false): ThemeOptions => { + let overrides; + if (isShipyard) { + overrides = createShipyardDarkModePalette(); + } else { + overrides = mode === 'light' ? createLightModePalette() : createDarkModePalette(); + } + + // create the palette from user's choice of light or dark mode + const { palette } = createTheme({ + palette: { + mode, + ...overrides, + }, + }); + + // then customise theme and components + return { + palette, + typography: { + fontFamily: [ + 'Open Sans', + 'sans-serif', + 'BlinkMacSystemFont', + 'Roboto', + 'Oxygen', + 'Ubuntu', + 'Helvetica Neue', + ].join(','), + fontSize: 16, + fontWeightRegular: 500, + button: { + textTransform: 'none', + fontWeight: '600', + }, + }, + shape: { + borderRadius: 8, + }, + transitions: { + duration: { + shortest: 150, + shorter: 200, + short: 250, + standard: 300, + complex: 375, + enteringScreen: 225, + leavingScreen: 195, + }, + easing: { + easeIn: 'cubic-bezier(0.4, 0, 1, 1)', + }, + }, + components: { + MuiButton: { + styleOverrides: { + sizeLarge: { + height: 55, + }, + }, + }, + MuiStepIcon: { + styleOverrides: { + root: { + '&.Mui-completed': { + color: nymPalette.success, + }, + '&.Mui-active': { + color: nymPalette.background.dark, + }, + }, + }, + }, + }, + }; +}; diff --git a/nym-connect/mobile/src/types/connection.ts b/nym-connect/mobile/src/types/connection.ts new file mode 100644 index 0000000000..8fb5234477 --- /dev/null +++ b/nym-connect/mobile/src/types/connection.ts @@ -0,0 +1,8 @@ +export enum ConnectionStatusKind { + disconnected = 'disconnected', + disconnecting = 'disconnecting', + connected = 'connected', + connecting = 'connecting', +} + +export type GatewayPerformance = 'Good' | 'Poor' | 'VeryPoor'; diff --git a/nym-connect/mobile/src/types/directory.ts b/nym-connect/mobile/src/types/directory.ts new file mode 100644 index 0000000000..55395192a1 --- /dev/null +++ b/nym-connect/mobile/src/types/directory.ts @@ -0,0 +1,14 @@ +export interface ServiceProvider { + id: string; + description: string; + address: string; + gateway: string; +} + +export interface Service { + id: string; + description: string; + items: ServiceProvider[]; +} + +export type Services = Service[]; diff --git a/nym-connect/mobile/src/types/error.ts b/nym-connect/mobile/src/types/error.ts new file mode 100644 index 0000000000..599b959c39 --- /dev/null +++ b/nym-connect/mobile/src/types/error.ts @@ -0,0 +1,4 @@ +export type Error = { + title: string; + message: string; +}; diff --git a/nym-connect/mobile/src/types/event.ts b/nym-connect/mobile/src/types/event.ts new file mode 100644 index 0000000000..66d1e514a6 --- /dev/null +++ b/nym-connect/mobile/src/types/event.ts @@ -0,0 +1,6 @@ +export type TauriEvent = { + payload: { + title: string; + message: string; + }; +}; diff --git a/nym-connect/mobile/src/types/gateway.ts b/nym-connect/mobile/src/types/gateway.ts new file mode 100644 index 0000000000..735a3cdb03 --- /dev/null +++ b/nym-connect/mobile/src/types/gateway.ts @@ -0,0 +1,4 @@ +export interface UserDefinedGateway { + isActive: boolean; + gateway?: string; +} diff --git a/nym-connect/mobile/src/types/index.ts b/nym-connect/mobile/src/types/index.ts new file mode 100644 index 0000000000..6bc150d410 --- /dev/null +++ b/nym-connect/mobile/src/types/index.ts @@ -0,0 +1,2 @@ +export * from './rust'; +export * from './connection'; diff --git a/nym-connect/mobile/src/types/rust/index.ts b/nym-connect/mobile/src/types/rust/index.ts new file mode 100644 index 0000000000..f8efcfe235 --- /dev/null +++ b/nym-connect/mobile/src/types/rust/index.ts @@ -0,0 +1 @@ +export * from './stateparams'; diff --git a/nym-connect/mobile/src/types/rust/stateparams.ts b/nym-connect/mobile/src/types/rust/stateparams.ts new file mode 100644 index 0000000000..b7d7032420 --- /dev/null +++ b/nym-connect/mobile/src/types/rust/stateparams.ts @@ -0,0 +1,7 @@ +/* eslint-disable camelcase */ +export interface TauriContractStateParams { + minimum_mixnode_pledge: string; + minimum_gateway_pledge: string; + mixnode_rewarded_set_size: number; + mixnode_active_set_size: number; +} diff --git a/nym-connect/mobile/src/typings/FC.d.ts b/nym-connect/mobile/src/typings/FC.d.ts new file mode 100644 index 0000000000..08ebdfa298 --- /dev/null +++ b/nym-connect/mobile/src/typings/FC.d.ts @@ -0,0 +1 @@ +declare type FCWithChildren

= React.FC>; diff --git a/nym-connect/mobile/src/typings/jpeg.d.ts b/nym-connect/mobile/src/typings/jpeg.d.ts new file mode 100644 index 0000000000..af2ed72913 --- /dev/null +++ b/nym-connect/mobile/src/typings/jpeg.d.ts @@ -0,0 +1,9 @@ +declare module '*.jpeg' { + const value: any; + export default value; +} + +declare module '*.jpg' { + const value: any; + export default value; +} diff --git a/nym-connect/mobile/src/typings/json.d.ts b/nym-connect/mobile/src/typings/json.d.ts new file mode 100644 index 0000000000..b72dd46ee5 --- /dev/null +++ b/nym-connect/mobile/src/typings/json.d.ts @@ -0,0 +1,4 @@ +declare module '*.json' { + const content: any; + export default content; +} diff --git a/nym-connect/mobile/src/typings/png.d.ts b/nym-connect/mobile/src/typings/png.d.ts new file mode 100644 index 0000000000..dd84df40a4 --- /dev/null +++ b/nym-connect/mobile/src/typings/png.d.ts @@ -0,0 +1,4 @@ +declare module '*.png' { + const content: any; + export default content; +} diff --git a/nym-connect/mobile/src/typings/svg.d.ts b/nym-connect/mobile/src/typings/svg.d.ts new file mode 100644 index 0000000000..091d25e210 --- /dev/null +++ b/nym-connect/mobile/src/typings/svg.d.ts @@ -0,0 +1,4 @@ +declare module '*.svg' { + const content: any; + export default content; +} diff --git a/nym-connect/mobile/src/typings/webp.d.ts b/nym-connect/mobile/src/typings/webp.d.ts new file mode 100644 index 0000000000..d594a17f53 --- /dev/null +++ b/nym-connect/mobile/src/typings/webp.d.ts @@ -0,0 +1,4 @@ +declare module '*.webp' { + const value: any; + export default value; +} diff --git a/nym-connect/mobile/src/typings/yaml.d.ts b/nym-connect/mobile/src/typings/yaml.d.ts new file mode 100644 index 0000000000..4c36d21ec0 --- /dev/null +++ b/nym-connect/mobile/src/typings/yaml.d.ts @@ -0,0 +1,9 @@ +declare module '*.yml' { + const content: { [key: string]: any }; + export default content; +} + +declare module '*.yaml' { + const content: { [key: string]: any }; + export default content; +} diff --git a/nym-connect/src/utils/index.ts b/nym-connect/mobile/src/utils/index.ts similarity index 100% rename from nym-connect/src/utils/index.ts rename to nym-connect/mobile/src/utils/index.ts diff --git a/nym-connect/mobile/tsconfig.eslint.json b/nym-connect/mobile/tsconfig.eslint.json new file mode 100644 index 0000000000..836be28a14 --- /dev/null +++ b/nym-connect/mobile/tsconfig.eslint.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "noEmit": true + }, + "include": [ + ".storybook/*.js", + "webpack.*.js", + "src/**/*.js", + "src/**/*.jsx", + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.stories.*", + ], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/nym-connect/mobile/tsconfig.json b/nym-connect/mobile/tsconfig.json new file mode 100644 index 0000000000..585e7203d8 --- /dev/null +++ b/nym-connect/mobile/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": false, + "jsx": "react-jsx", + "sourceMap": true, + "baseUrl": ".", + "paths": { + "@assets/*": ["../assets/*"] + } + }, + "exclude": ["node_modules", "dist", "jest.config.js", "webpack.config.js", "webpack.prod.js", "webpack.common.js", "target", "src-tauri"] +} diff --git a/nym-connect/mobile/webpack.common.js b/nym-connect/mobile/webpack.common.js new file mode 100644 index 0000000000..c835404dda --- /dev/null +++ b/nym-connect/mobile/webpack.common.js @@ -0,0 +1,55 @@ +const path = require('path'); +const { mergeWithRules } = require('webpack-merge'); +const { webpackCommon } = require('@nymproject/webpack'); + +const entry = { + app: path.resolve(__dirname, 'src/index.tsx'), +}; + +module.exports = mergeWithRules({ + module: { + rules: { + test: 'match', + use: 'replace', + }, + }, +})( + webpackCommon(__dirname, [ + { filename: 'index.html', chunks: ['app'], template: path.resolve(__dirname, 'public/index.html') }, + ]), + { + module: { + rules: [ + { + test: /\.mdx?$/, + use: [ + { + loader: '@mdx-js/loader', + /** @type {import('@mdx-js/loader').Options} */ + options: {}, + }, + ], + }, + { + test: /\.ya?ml$/, + type: 'asset/resource', + use: [ + { + loader: 'yaml-loader', + options: { + asJSON: true, + }, + }, + ], + }, + ], + }, + entry, + output: { + clean: true, + path: path.resolve(__dirname, 'dist'), + filename: '[name].bundle.js', + publicPath: '/', + }, + }, +); diff --git a/nym-connect/mobile/webpack.dev.js b/nym-connect/mobile/webpack.dev.js new file mode 100644 index 0000000000..48f08918cf --- /dev/null +++ b/nym-connect/mobile/webpack.dev.js @@ -0,0 +1,69 @@ +const { mergeWithRules } = require('webpack-merge'); +const webpack = require('webpack'); +const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); +const ReactRefreshTypeScript = require('react-refresh-typescript'); +const commonConfig = require('./webpack.common'); + +module.exports = mergeWithRules({ + module: { + rules: { + test: 'match', + use: 'replace', + }, + }, +})(commonConfig, { + mode: 'development', + devtool: 'inline-source-map', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + options: { + getCustomTransformers: () => ({ + before: [ReactRefreshTypeScript()], + }), + // `ts-loader` does not work with HMR unless `transpileOnly` is used. + // If you need type checking, `ForkTsCheckerWebpackPlugin` is an alternative. + transpileOnly: true, + }, + }, + ], + }, + plugins: [ + new ReactRefreshWebpackPlugin(), + + // this can be included automatically by the dev server, however build mode fails if missing + new webpack.HotModuleReplacementPlugin(), + ], + + // recommended for faster rebuild + optimization: { + runtimeChunk: true, + removeAvailableModules: false, + removeEmptyChunks: false, + splitChunks: false, + }, + + cache: { + type: 'filesystem', + buildDependencies: { + // restart on config change + config: ['./webpack.dev.js'], + }, + }, + + devServer: { + port: 9000, + compress: true, + historyApiFallback: true, + // disable this because on android it makes reloading infinity loop + hot: false, + host: 'local-ipv4', + allowedHosts: 'all', + client: { + overlay: false, + }, + }, +}); diff --git a/nym-connect/mobile/webpack.prod.js b/nym-connect/mobile/webpack.prod.js new file mode 100644 index 0000000000..881192cf19 --- /dev/null +++ b/nym-connect/mobile/webpack.prod.js @@ -0,0 +1,15 @@ +const path = require('path'); +const { default: merge } = require('webpack-merge'); +const common = require('./webpack.common'); + +const entry = { + app: path.resolve(__dirname, 'src/index.tsx'), +}; + +module.exports = merge(common, { + mode: 'production', + node: { + __dirname: false, + }, + entry, +}); diff --git a/nym-connect/src-tauri/src/operations/connection/status.rs b/nym-connect/src-tauri/src/operations/connection/status.rs deleted file mode 100644 index ccf02400f3..0000000000 --- a/nym-connect/src-tauri/src/operations/connection/status.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::error::Result; -use std::sync::Arc; - -use tokio::sync::RwLock; - -use crate::models::ConnectionStatusKind; -use crate::state::State; - -#[tauri::command] -pub async fn get_connection_status( - state: tauri::State<'_, Arc>>, -) -> Result { - let state = state.read().await; - Ok(state.get_status()) -} diff --git a/nym-connect/src-tauri/src/operations/directory/mod.rs b/nym-connect/src-tauri/src/operations/directory/mod.rs deleted file mode 100644 index efc2ba175b..0000000000 --- a/nym-connect/src-tauri/src/operations/directory/mod.rs +++ /dev/null @@ -1,51 +0,0 @@ -use itertools::Itertools; - -use crate::error::Result; -use crate::models::{DirectoryService, HarbourMasterService, PagedResult}; - -static SERVICE_PROVIDER_WELLKNOWN_URL: &str = - "https://nymtech.net/.wellknown/connect/service-providers.json"; - -static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100"; - -#[tauri::command] -pub async fn get_services() -> Result> { - log::trace!("Fetching services"); - let res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL) - .await? - .json::>() - .await?; - log::trace!("Received: {:#?}", res); - - // TODO: get paged - log::trace!("Fetching active services"); - let active_services = reqwest::get(HARBOUR_MASTER_URL) - .await? - .json::>() - .await?; - log::trace!("Active: {:#?}", active_services); - - let mut filtered: Vec = vec![]; - - for service in &res { - let items: _ = service - .items - .clone() - .into_iter() - .filter(|sp| { - active_services - .items - .iter() - .any(|active| active.service_provider_client_id == sp.address) - }) - .collect_vec(); - log::trace!("service = {} has {} items", service.id, items.len()); - filtered.push(DirectoryService { - id: service.id.clone(), - description: service.description.clone(), - items, - }) - } - - Ok(filtered) -} diff --git a/nym-connect/src/App.tsx b/nym-connect/src/App.tsx deleted file mode 100644 index 9d5afd1427..0000000000 --- a/nym-connect/src/App.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, { useEffect } from 'react'; -import { DateTime } from 'luxon'; -import { forage } from '@tauri-apps/tauri-forage'; -import { ConnectionStatusKind } from './types'; -import { useClientContext } from './context/main'; -import { DefaultLayout } from './layouts/DefaultLayout'; -import { ConnectedLayout } from './layouts/ConnectedLayout'; -import { HelpGuideLayout } from './layouts/HelpGuideLayout'; -import { useTauriEvents } from './utils'; - -export const App: React.FC = () => { - const context = useClientContext(); - const [busy, setBusy] = React.useState(); - const [showInfoModal, setShowInfoModal] = React.useState(false); - useTauriEvents('help://clear-storage', (_event) => { - console.log('About to clear local storage...'); - // clear local storage - try { - forage.clear()(); - console.log('Local storage cleared'); - } catch (e) { - console.error('Failed to clear local storage', e); - } - }); - - const handleConnectClick = React.useCallback(async () => { - const currentStatus = context.connectionStatus; - if (currentStatus === ConnectionStatusKind.connected || currentStatus === ConnectionStatusKind.disconnected) { - setBusy(true); - - // eslint-disable-next-line default-case - switch (currentStatus) { - case ConnectionStatusKind.disconnected: - await context.startConnecting(); - context.setConnectedSince(DateTime.now()); - break; - case ConnectionStatusKind.connected: - await context.startDisconnecting(); - context.setConnectedSince(undefined); - break; - } - setBusy(false); - } - }, [context.connectionStatus]); - - useEffect(() => { - if (context.connectionStatus === ConnectionStatusKind.connected) setShowInfoModal(true); - }, [context.connectionStatus]); - - if (context.showHelp) return ; - - if ( - context.connectionStatus === ConnectionStatusKind.disconnected || - context.connectionStatus === ConnectionStatusKind.connecting - ) { - return ( - - ); - } - - return ( - setShowInfoModal(false)} - status={context.connectionStatus} - busy={busy} - onConnectClick={handleConnectClick} - ipAddress="127.0.0.1" - port={1080} - connectedSince={context.connectedSince} - serviceProvider={context.serviceProvider} - stats={[ - { - label: 'in:', - totalBytes: 1024, - rateBytesPerSecond: 1024 * 1024 * 1024 + 10, - }, - { - label: 'out:', - totalBytes: 1024 * 1024 * 1024 * 1024 * 20, - rateBytesPerSecond: 1024 * 1024 + 10, - }, - ]} - /> - ); -}; diff --git a/nym-connect/src/components/AppWindowFrame.tsx b/nym-connect/src/components/AppWindowFrame.tsx deleted file mode 100644 index 80bed93f77..0000000000 --- a/nym-connect/src/components/AppWindowFrame.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { Box } from '@mui/material'; -import { CustomTitleBar } from './CustomTitleBar'; -import { AppVersion } from './AppVersion'; - -export const AppWindowFrame: React.FC = ({ children }) => ( - - - {children} - -); diff --git a/nym-connect/src/components/ConnectionStatus.tsx b/nym-connect/src/components/ConnectionStatus.tsx deleted file mode 100644 index c2bfcc82f0..0000000000 --- a/nym-connect/src/components/ConnectionStatus.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react'; -import { Box, CircularProgress, Typography } from '@mui/material'; -import { DateTime } from 'luxon'; -import { ConnectionStatusKind } from '../types'; -import { ServiceProvider } from '../types/directory'; - -const FONT_SIZE = '10px'; -const FONT_WEIGHT = '600'; -const FONT_STYLE = 'normal'; - -const ConnectionStatusContent: React.FC<{ - status: ConnectionStatusKind; -}> = ({ status }) => { - switch (status) { - case ConnectionStatusKind.connected: - return ( - - Connected - - ); - case ConnectionStatusKind.disconnecting: - return ( - - - - Disconnecting... - - - ); - case ConnectionStatusKind.connecting: - return ( - - - - Connecting... - - - ); - case ConnectionStatusKind.disconnected: - return ( - - You are not protected - - ); - default: - return null; - } -}; - -export const ConnectionStatus: React.FC<{ - status: ConnectionStatusKind; - connectedSince?: DateTime; - serviceProvider?: ServiceProvider; -}> = ({ status, connectedSince, serviceProvider }) => { - const color = - status === ConnectionStatusKind.connected || status === ConnectionStatusKind.disconnecting - ? '#21D072' - : 'warning.main'; - - return ( - <> - - - - - {serviceProvider && ( - - To {serviceProvider.description} - - )} - - - ); -}; diff --git a/nym-connect/src/components/CustomTitleBar.tsx b/nym-connect/src/components/CustomTitleBar.tsx deleted file mode 100644 index 41c71137b9..0000000000 --- a/nym-connect/src/components/CustomTitleBar.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { ArrowBack, Close, HelpOutline, Minimize } from '@mui/icons-material'; -import { Box, IconButton } from '@mui/material'; -import { NymWordmark } from '@nymproject/react/logo/NymWordmark'; -import { appWindow } from '@tauri-apps/api/window'; -import { useClientContext } from 'src/context/main'; - -const customTitleBarStyles = { - titlebar: { - background: '#1D2125', - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - padding: '16px', - paddingBottom: '0px', - borderTopLeftRadius: '12px', - borderTopRightRadius: '12px', - }, -}; - -const CustomButton = ({ Icon, onClick }: { Icon: React.JSXElementConstructor; onClick: () => void }) => ( - - - -); - -export const CustomTitleBar = () => { - const { showHelp, handleShowHelp } = useClientContext(); - return ( - - {/* set width to keep logo centered */} - - { - handleShowHelp(); - }} - /> - - - - - - appWindow.minimize()} /> - appWindow.close()} /> - - - ); -}; diff --git a/nym-connect/src/components/HelpPageImage.tsx b/nym-connect/src/components/HelpPageImage.tsx deleted file mode 100644 index 27a8dcdca3..0000000000 --- a/nym-connect/src/components/HelpPageImage.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export const HelpImage = ({ img, imageDescription }: { img: any; imageDescription: string }) => ( - {imageDescription} -); diff --git a/nym-connect/src/context/main.tsx b/nym-connect/src/context/main.tsx deleted file mode 100644 index 945952f2ef..0000000000 --- a/nym-connect/src/context/main.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; -import { DateTime } from 'luxon'; -import { invoke } from '@tauri-apps/api'; -import type { UnlistenFn } from '@tauri-apps/api/event'; -import { listen } from '@tauri-apps/api/event'; -import { forage } from '@tauri-apps/tauri-forage'; -import { Error } from 'src/types/error'; -import { TauriEvent } from 'src/types/event'; -import { getVersion } from '@tauri-apps/api/app'; -import { ConnectionStatusKind } from '../types'; -import { ConnectionStatsItem } from '../components/ConnectionStats'; -import { ServiceProvider, Services } from '../types/directory'; - -const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed'; - -type ModeType = 'light' | 'dark'; - -export type TClientContext = { - mode: ModeType; - appVersion?: string; - connectionStatus: ConnectionStatusKind; - connectionStats?: ConnectionStatsItem[]; - connectedSince?: DateTime; - services?: Services; - serviceProvider?: ServiceProvider; - showHelp: boolean; - error?: Error; - - setMode: (mode: ModeType) => void; - clearError: () => void; - handleShowHelp: () => void; - setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void; - setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void; - setConnectedSince: (connectedSince: DateTime | undefined) => void; - setServiceProvider: (serviceProvider?: ServiceProvider) => void; - - startConnecting: () => Promise; - startDisconnecting: () => Promise; -}; - -export const ClientContext = createContext({} as TClientContext); - -export const ClientContextProvider = ({ children }: { children: React.ReactNode }) => { - const [mode, setMode] = useState('dark'); - const [connectionStatus, setConnectionStatus] = useState(ConnectionStatusKind.disconnected); - const [connectionStats, setConnectionStats] = useState(); - const [connectedSince, setConnectedSince] = useState(); - const [services, setServices] = React.useState([]); - const [serviceProvider, setRawServiceProvider] = React.useState(); - const [showHelp, setShowHelp] = useState(false); - const [error, setError] = useState(); - const [appVersion, setAppVersion] = useState(); - - const getAppVersion = async () => { - const version = await getVersion(); - setAppVersion(version); - }; - - useEffect(() => { - invoke('get_services').then((result) => { - setServices(result as Services); - }); - getAppVersion(); - }, []); - - useEffect(() => { - // when mounting, load the connection state (needed for the Growth window, that checks the connection state) - (async () => { - const currentStatus: ConnectionStatusKind = await invoke('get_connection_status'); - setConnectionStatus(currentStatus); - })(); - }, []); - - useEffect(() => { - const unlisten: UnlistenFn[] = []; - - // TODO: fix typings - listen(TAURI_EVENT_STATUS_CHANGED, (event) => { - const { status } = event.payload as any; - console.log(TAURI_EVENT_STATUS_CHANGED, { status, event }); - setConnectionStatus(status); - }) - .then((result) => { - unlisten.push(result); - }) - .catch((e) => console.log(e)); - - listen('socks5-event', (e: TauriEvent) => { - setError(e.payload); - }).then((result) => { - unlisten.push(result); - }); - - return () => { - unlisten.forEach((unsubscribe) => unsubscribe()); - }; - }, []); - - const startConnecting = useCallback(async () => { - try { - await invoke('start_connecting'); - } catch (e) { - setError({ title: 'Could not connect', message: e as string }); - console.log(e); - } - }, []); - - const startDisconnecting = useCallback(async () => { - try { - await invoke('start_disconnecting'); - } catch (e) { - console.log(e); - } - }, []); - - const setSpInStorage = async (sp: ServiceProvider) => { - await forage.setItem({ - key: 'nym-connect-sp', - value: sp, - } as any)(); - }; - - const setServiceProvider = useCallback(async (newServiceProvider?: ServiceProvider) => { - await invoke('set_gateway', { gateway: newServiceProvider?.gateway }); - await invoke('set_service_provider', { serviceProvider: newServiceProvider?.address }); - if (newServiceProvider) { - await setSpInStorage(newServiceProvider); - } - setRawServiceProvider(newServiceProvider); - }, []); - - const getSpFromStorage = async () => { - try { - const spFromStorage = await forage.getItem({ key: 'nym-connect-sp' })(); - if (spFromStorage) { - setRawServiceProvider(spFromStorage); - setServiceProvider(spFromStorage); - } - } catch (e) { - console.warn(e); - } - }; - - const handleShowHelp = () => setShowHelp((show) => !show); - - const clearError = () => setError(undefined); - - useEffect(() => { - const validityCheck = async () => { - if (services.length > 0 && serviceProvider) { - const isValid = services.some(({ items }) => items.some(({ id }) => id === serviceProvider.id)); - if (!isValid) { - console.warn('invalid SP, cleaning local storage'); - await forage.removeItem({ - key: 'nym-connect-sp', - })(); - setRawServiceProvider(undefined); - } - } - }; - validityCheck(); - }, [services, serviceProvider]); - - useEffect(() => { - getSpFromStorage(); - }, []); - - const contextValue = useMemo( - () => ({ - mode, - appVersion, - setMode, - error, - clearError, - connectionStatus, - setConnectionStatus, - connectionStats, - setConnectionStats, - connectedSince, - setConnectedSince, - startConnecting, - startDisconnecting, - services, - serviceProvider, - setServiceProvider, - showHelp, - handleShowHelp, - }), - [ - appVersion, - mode, - error, - connectedSince, - showHelp, - connectionStatus, - connectionStats, - connectedSince, - services, - serviceProvider, - ], - ); - - return {children}; -}; - -export const useClientContext = () => useContext(ClientContext); diff --git a/nym-connect/src/context/mocks/main.tsx b/nym-connect/src/context/mocks/main.tsx deleted file mode 100644 index 9b09b6dc71..0000000000 --- a/nym-connect/src/context/mocks/main.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import { ConnectionStatusKind } from 'src/types'; -import { ClientContext, TClientContext } from '../main'; - -const mockValues: TClientContext = { - appVersion: 'v1.x.x', - mode: 'dark', - connectionStatus: ConnectionStatusKind.disconnected, - services: [], - showHelp: false, - serviceProvider: { id: '1', description: 'Keybase service provider', gateway: 'abc123', address: '123abc' }, - setMode: () => {}, - clearError: () => {}, - handleShowHelp: () => {}, - setConnectedSince: () => {}, - setConnectionStats: () => {}, - setConnectionStatus: () => {}, - setServiceProvider: () => {}, - startConnecting: async () => {}, - startDisconnecting: async () => {}, -}; - -export const MockProvider: React.FC<{ - children?: React.ReactNode; - connectionStatus?: ConnectionStatusKind; -}> = ({ connectionStatus = ConnectionStatusKind.disconnected, children }) => ( - {children} -); diff --git a/nym-connect/src/index.tsx b/nym-connect/src/index.tsx deleted file mode 100644 index bd41e06822..0000000000 --- a/nym-connect/src/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { ErrorBoundary } from 'react-error-boundary'; -import { ClientContextProvider } from './context/main'; -import { ErrorFallback } from './components/Error'; -import { NymMixnetTheme } from './theme'; -import { App } from './App'; -import { AppWindowFrame } from './components/AppWindowFrame'; -import { TestAndEarnContextProvider } from './components/Growth/context/TestAndEarnContext'; - -const root = document.getElementById('root'); - -ReactDOM.render( - - - - - - - - - - - , - root, -); diff --git a/nym-connect/src/layouts/ConnectedLayout.tsx b/nym-connect/src/layouts/ConnectedLayout.tsx deleted file mode 100644 index 0bf61fd4c0..0000000000 --- a/nym-connect/src/layouts/ConnectedLayout.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import { Box, Divider } from '@mui/material'; -import { DateTime } from 'luxon'; -import { IpAddressAndPortModal } from 'src/components/IpAddressAndPortModal'; -import { ConnectionTimer } from 'src/components/ConntectionTimer'; -import { ConnectionStatus } from '../components/ConnectionStatus'; -import { ConnectionStatusKind } from '../types'; -import { ConnectionStatsItem } from '../components/ConnectionStats'; -import { ConnectionButton } from '../components/ConnectionButton'; -import { IpAddressAndPort } from '../components/IpAddressAndPort'; -import { ServiceProvider } from '../types/directory'; -import { TestAndEarnButtonArea } from '../components/Growth/TestAndEarnButtonArea'; - -export const ConnectedLayout: React.FC<{ - status: ConnectionStatusKind; - stats: ConnectionStatsItem[]; - ipAddress: string; - port: number; - connectedSince?: DateTime; - busy?: boolean; - showInfoModal: boolean; - isError?: boolean; - handleCloseInfoModal: () => void; - onConnectClick?: (status: ConnectionStatusKind) => void; - serviceProvider?: ServiceProvider; -}> = ({ - status, - showInfoModal, - handleCloseInfoModal, - ipAddress, - port, - connectedSince, - busy, - isError, - serviceProvider, - onConnectClick, -}) => ( - <> - - - - - - - {/* */} - - - - -); diff --git a/nym-connect/src/layouts/DefaultLayout.tsx b/nym-connect/src/layouts/DefaultLayout.tsx deleted file mode 100644 index 5c414a5bc1..0000000000 --- a/nym-connect/src/layouts/DefaultLayout.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import { Box, Typography } from '@mui/material'; -import { ConnectionStatus } from 'src/components/ConnectionStatus'; -import { ConnectionTimer } from 'src/components/ConntectionTimer'; -import { InfoModal } from 'src/components/InfoModal'; -import { Error } from 'src/types/error'; -import { ConnectionButton } from '../components/ConnectionButton'; -import { ServiceSelector } from '../components/ServiceSelector'; -import { useClientContext } from '../context/main'; -import { ConnectionStatusKind } from '../types'; -import { Services } from '../types/directory'; -import { TestAndEarnButtonArea } from '../components/Growth/TestAndEarnButtonArea'; -import { AppVersion } from '../components/AppVersion'; - -export const DefaultLayout: React.FC<{ - error?: Error; - status: ConnectionStatusKind; - services?: Services; - busy?: boolean; - isError?: boolean; - clearError: () => void; - onConnectClick?: (status: ConnectionStatusKind) => void; -}> = ({ status, error, services, busy, isError, onConnectClick, clearError }) => { - const context = useClientContext(); - - return ( - - {error && } - - - - Connect to the Nym
mixnet for privacy. -
- - This is experimental software. Do not rely on it for strong anonymity (yet). - -
- - - - - -
- ); -}; diff --git a/nym-connect/src/layouts/HelpGuideLayout.tsx b/nym-connect/src/layouts/HelpGuideLayout.tsx deleted file mode 100644 index d56fd6b0ae..0000000000 --- a/nym-connect/src/layouts/HelpGuideLayout.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useState } from 'react'; -import { HelpPage } from 'src/components/HelpPage'; -import { Button, Link, Stack } from '@mui/material'; -import Image1 from '../assets/help-step-one.png'; -import Image2 from '../assets/help-step-two.png'; -import Image3 from '../assets/help-step-three.png'; -import Image4 from '../assets/help-step-four.png'; - -export const HelpGuideLayout = () => { - const [step, setStep] = useState(0); - - if (step === 1) - return ( - setStep(2)} - /> - ); - - if (step === 2) - return ( - setStep(1)} - onNext={() => setStep(3)} - /> - ); - - if (step === 3) - return ( - setStep(2)} - onNext={() => setStep(4)} - /> - ); - - if (step === 4) - return ( - setStep(3)} - /> - ); - - return ( - - - - - ); -}; diff --git a/nym-outfox/Cargo.toml b/nym-outfox/Cargo.toml new file mode 100644 index 0000000000..48a05c842c --- /dev/null +++ b/nym-outfox/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "nym-outfox" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rayon = "1.5.1" +blake3 = "1.3" +zeroize = "1.5" +chacha20 = "0.9.0" +curve25519-dalek = "3.2" +chacha20poly1305 = "0.10.1" +# Need this star over here to pull in js into getrandom +getrandom = { version = "*", features = ["js"] } +thiserror = "1" + +[dev-dependencies] +criterion = "0.4" +fastrand = "1.8" diff --git a/nym-outfox/src/error.rs b/nym-outfox/src/error.rs new file mode 100644 index 0000000000..afbba2fc4b --- /dev/null +++ b/nym-outfox/src/error.rs @@ -0,0 +1,20 @@ +use crate::lion::MIN_MESSAGE_LEN; +use chacha20::cipher::InvalidLength; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum OutFoxError { + #[error("Lengths mismatch, expected: {expected}, got: {got}")] + LenMismatch { expected: usize, got: usize }, + #[error("{source}")] + ChaCha20InvalidLength { + #[from] + source: InvalidLength, + }, + #[error("ChaCha20Poly1305 - Opaque error")] + ChaCha20Poly1305Error, + #[error("Key length must be 32 bytes")] + InvalidKeyLength, + #[error("Message length must be greater then {MIN_MESSAGE_LEN} bytes")] + InvalidMessageLength, +} diff --git a/nym-outfox/src/format.rs b/nym-outfox/src/format.rs new file mode 100644 index 0000000000..2ddf895e1f --- /dev/null +++ b/nym-outfox/src/format.rs @@ -0,0 +1,267 @@ +//! # The `outfox` format +//! +//! We define a simple mix packet format geared towards simplicity and performance with the features that are +//! specifically required by a stratified mix topology. +//! +//! `Outfox` assumes that all +//! paths are the same length (no need to hide path lengths), mixes are arranged in layers and therefore +//! know their position in a message path (no need to hide this position). These assumptions allow us +//! to do away with some of the padding traditionally used; further we prioritize efficient computation +//! over very low-bandwidth, as it seems the rate of public key operations is a bottleneck for mixes +//! rather than the availablility of bandwidth. +//! +//! ## Overview and Parameters +//! +//! In a mix network with a stratified topology packets are mixed by nodes at each of the layers. Each layer +//! 'strips' the packet from one layer of encryption, recovers the address of the mix at the next layer, and +//! passes the decoded packet to them. An identifier per processed message is stored and checked to prevent +//! replays of processed messages at each layer. Additional measures, such as adding delays, adding dummy +//! traffic or dropping messages can be empued at each mix to frustrate traffic analysis. +//! +//! +//! A layer of mix processing is defined by three parameters, includes in the structure [MixStageParameters]: +//! * The `routing_information_length_bytes` (`R`) states the number of bytes representing +//! routing information at this layer. +//! * The `remaining_header_length_bytes` (`H`) represents the remaining bytes of the packet header. +//! * The `payload_length_bytes` (`P`). +//! +//! In addition we define two system-wide constants, namely `GROUPELEMENTBYTES` (`GE`=32) and +//! `TAGBYTES` (`T`=24). +//! +//! ## Packet format, decoding +//! +//! A mix at this layer takes in messages of length `GE+T+R+H+P`, and outputs messages of length `H+P`. +//! +//! An input message is processed as follows: +//! +//! * The input packet is parsed as a `[Pk, Tag, Header, Payload]` of length `[GE, T, R+H, P]` respectivelly. +//! * A master key is derived by performing scalar multiplication with the mix secret 's', ie `K = s * Pk`. +//! The master key is stored and checked for duplicates (if it is found processing ends.) +//! * The master key is used to perform AEAD decryption of the `Header` with an IV of zeros and the `tag`. If +//! decryption fails processing ends. Otherwise the Header is parsed as `[Routing, Next_Header]` of length +//! `[R, H]` respectivelly. The routing data `Routing` can be used by the mix to dertermine the next mix. +//! * Finally, the master key is used to perform lion decoding of the `Payload` into `Next_Payload`. +//! * The output packet for the next mix is `[Next_Header, Next_Payload]`. +//! +//! As an AEAD we use `chacha20poly1305_ietf` and for public key operations we use `curve25519`. +//! +//! ## Packet encoding +//! +//! Encoding is +//! performed layer by layer starting with the last hop on the route, and ending with the first. At each stage +//! of encoding a new Secret key `Sk` and corresponding `Pk` is chosen. The layer master key for the layer is +//! derived using the mix public key. And the master key is used to AEAD encrypt the concatenation of the +//! routing data for the layer, and the remaining Header; separately the master key is used to lion encrypt +//! the payload. The process is repeated for each layer (from last to first) to construct the full message. + +use chacha20poly1305::AeadInPlace; +use chacha20poly1305::ChaCha20Poly1305; +use chacha20poly1305::KeyInit; + +use chacha20poly1305::Tag; +use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; +use curve25519_dalek::montgomery::MontgomeryPoint; +use curve25519_dalek::scalar::Scalar; + +use std::convert::TryInto; + +const GROUPELEMENTBYTES: usize = 32; +const TAGBYTES: usize = 16; + +use std::ops::Range; + +use crate::error::OutFoxError; +use crate::lion::*; + +/// A structure that holds mix packet construction parameters. These incluse the length +/// of the routing information at each hop, the number of hops, and the payload length. +pub struct MixCreationParameters { + /// The routing length is inner first, so \[0\] is the innermost routing length, etc (in bytes) + pub routing_information_length_by_stage: Vec, + /// The payload length (in bytes) + pub payload_length_bytes: usize, +} + +impl MixCreationParameters { + /// Create a set of parameters for a mix packet format. + pub fn new(payload_length_bytes: usize) -> MixCreationParameters { + MixCreationParameters { + routing_information_length_by_stage: Vec::new(), + payload_length_bytes, + } + } + + /// Add another outer layer containing some byte length of routing data. + pub fn add_outer_layer(&mut self, routing_information_length_bytes: usize) { + self.routing_information_length_by_stage + .push(routing_information_length_bytes); + } + + /// The length of the buffer needed to build a packet. + pub fn total_packet_length(&self) -> usize { + let mut len = self.payload_length_bytes; + for stage_len in &self.routing_information_length_by_stage { + len += stage_len + GROUPELEMENTBYTES + TAGBYTES + } + len + } + + /// Get the mix packet parameters for a single stage of mixing. + pub fn get_stage_params(&self, layer_number: usize) -> (Range, MixStageParameters) { + assert!(layer_number < self.routing_information_length_by_stage.len()); + + let mut remaining_header_length_bytes = 0; + for (i, stage_len) in self.routing_information_length_by_stage.iter().enumerate() { + if i == layer_number { + let params = MixStageParameters { + routing_information_length_bytes: *stage_len, + remaining_header_length_bytes, + payload_length_bytes: self.payload_length_bytes, + }; + + let total_size = self.total_packet_length(); + let inner_size = params.incoming_packet_length(); + + return (total_size - inner_size..total_size, params); + } else { + remaining_header_length_bytes += stage_len + GROUPELEMENTBYTES + TAGBYTES; + } + } + + unreachable!(); + } +} + +/// A structure representing the parameters of a single stage of mixing. +pub struct MixStageParameters { + /// The routing information length for this stage of mixing + pub routing_information_length_bytes: usize, + /// The reamining header length for this stage of mixing + pub remaining_header_length_bytes: usize, + /// The payload length + pub payload_length_bytes: usize, +} + +impl MixStageParameters { + pub fn incoming_packet_length(&self) -> usize { + GROUPELEMENTBYTES + TAGBYTES + self.outgoing_packet_length() + } + + pub fn outgoing_packet_length(&self) -> usize { + self.routing_information_length_bytes + + self.remaining_header_length_bytes + + self.payload_length_bytes + } + + pub fn pub_element_range(&self) -> Range { + 0..GROUPELEMENTBYTES + } + + pub fn tag_range(&self) -> Range { + GROUPELEMENTBYTES..GROUPELEMENTBYTES + TAGBYTES + } + + pub fn routing_data_range(&self) -> Range { + GROUPELEMENTBYTES + TAGBYTES + ..GROUPELEMENTBYTES + TAGBYTES + self.routing_information_length_bytes + } + + pub fn header_range(&self) -> Range { + GROUPELEMENTBYTES + TAGBYTES + ..GROUPELEMENTBYTES + + TAGBYTES + + self.routing_information_length_bytes + + self.remaining_header_length_bytes + } + + pub fn payload_range(&self) -> Range { + self.incoming_packet_length() - self.payload_length_bytes..self.incoming_packet_length() + } + + pub fn encode_mix_layer( + &self, + buffer: &mut [u8], + user_secret_key: &Scalar, + mix_public_key: &MontgomeryPoint, + routing_data: &[u8], + ) -> Result { + if buffer.len() != self.incoming_packet_length() { + return Err(OutFoxError::LenMismatch { + expected: buffer.len(), + got: self.incoming_packet_length(), + }); + } + + if routing_data.len() != self.routing_information_length_bytes { + return Err(OutFoxError::LenMismatch { + expected: routing_data.len(), + got: self.routing_information_length_bytes, + }); + } + + let user_public_key = (&ED25519_BASEPOINT_TABLE * user_secret_key).to_montgomery(); + let shared_key = user_secret_key * mix_public_key; + + // Copy rounting data into buffer + buffer[self.routing_data_range()].copy_from_slice(routing_data); + + // Perform the AEAD + let header_aead_key = ChaCha20Poly1305::new_from_slice(&shared_key.0[..])?; + // TODO: Should this be all 0s? + let nonce = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + let tag = header_aead_key + .encrypt_in_place_detached(&nonce.into(), &[], &mut buffer[self.header_range()]) + .map_err(|_| OutFoxError::ChaCha20Poly1305Error)?; + + // Copy Tag into buffer + buffer[self.tag_range()].copy_from_slice(&tag[..]); + + // Copy own public key into buffer + buffer[self.pub_element_range()].copy_from_slice(&user_public_key.0[..]); + + // Do a round of LION on the payload + lion_transform_encrypt(&mut buffer[self.payload_range()], &shared_key.0)?; + + Ok(shared_key) + } + + pub fn decode_mix_layer( + &self, + buffer: &mut [u8], + mix_secret_key: &Scalar, + ) -> Result { + // Check the length of the incoming buffer is correct. + if buffer.len() != self.incoming_packet_length() { + return Err(OutFoxError::LenMismatch { + expected: buffer.len(), + got: self.incoming_packet_length(), + }); + } + + // Derive the shared key for this packet + let user_public_key = MontgomeryPoint(buffer[self.pub_element_range()].try_into().unwrap()); + let shared_key = mix_secret_key * user_public_key; + + // Compute the AEAD and check the Tag, if wrong return Err + let header_aead_key = ChaCha20Poly1305::new_from_slice(&shared_key.0[..]).unwrap(); + let nonce = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + let tag_bytes = buffer[self.tag_range()].to_vec(); + let tag = Tag::from_slice(&tag_bytes); + + header_aead_key + .decrypt_in_place_detached( + &nonce.into(), + &[], + &mut buffer[self.header_range()], + tag.as_slice().try_into().unwrap(), + ) + .map_err(|_| OutFoxError::ChaCha20Poly1305Error)?; + + // Do a round of LION on the payload + lion_transform_decrypt(&mut buffer[self.payload_range()], &shared_key.0)?; + + Ok(shared_key) + } +} diff --git a/nym-outfox/src/lib.rs b/nym-outfox/src/lib.rs new file mode 100644 index 0000000000..fbfcc32078 --- /dev/null +++ b/nym-outfox/src/lib.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod format; +pub mod lion; diff --git a/nym-outfox/src/lion.rs b/nym-outfox/src/lion.rs new file mode 100644 index 0000000000..e4ea5e2c55 --- /dev/null +++ b/nym-outfox/src/lion.rs @@ -0,0 +1,114 @@ +//! # The lion all-or-nothing transform +//! +//! The lion transform implements a keyed permutation (block cipher) with a +//! variable length block size. It takes a key of 32 bytes, and a message of +//! length >= 48 bytes. +//! +//! The cryptographic primitives used to implement the transform are a +//! stream cipher `PRF1(IV, KEY)` (using `crypto_stream_xsalsa20`), a message authentication +//! code `PRF2(MSG, KEY)` (using `HMAC-SHA-512-256`) and a key derivation function `KDF(KEY, ID)` +//! (using `Blake2b`). +//! +//! The message to encode is split into two parts `M = [L0, R0]`, where L is 24 bytes, and +//! R is the remaining of the message. +//! +//! Encoding then proceeds in 3 steps: +//! * `R1 = PRF1(L0, KDF(key, subkey_0)) XOR R0;` +//! * `L1 = PRF2(R1, KDF(key, subkey_1)) XOR L0;` +//! * `R2 = PRF1(L1, KDF(key, subkey_2)) XOR R1;` +//! +//! The output of the transform is the concatenated byte string `M' = [L1, R2]` which has the same +//! length as the original message. +//! +//! ## Manual key schedule. +//! +//! If you just want to encode / decode using lion as a wide-block block cipher simply use the +//! [lion_transform_encrypt] and [lion_transform_decrypt] functions. +//! +//! If you know what you are doing you can determine your own key schedule for the transform. The +//! key schedule for encypt and decrypt are [1, 2, 3] and [3, 2, 1] respectivelly. You may define +//! a key schedule that is symmetric (such as [1, 2, 1]) to build a transform T(k,m) that has the +//! property m = T(k, T(k, m)). + +use chacha20::cipher::{KeyIvInit, StreamCipher}; +use chacha20::Key; +use chacha20::XChaCha20; +use chacha20::XNonce; +use zeroize::Zeroize; + +use crate::error::OutFoxError; + +pub const MIN_MESSAGE_LEN: usize = 24 * 2; +const CONTEXT: &str = "LIONKEYS"; +const TAG_LEN: usize = 24; + +/// The lion transform encryption function. +/// +/// The `key` must be 32 bytes, and the `message` >= 48. The message is +/// mutated to the encrypted message. +pub fn lion_transform_encrypt(message: &mut [u8], key: &[u8]) -> Result<(), OutFoxError> { + lion_transform(message, key, [1, 2, 3]) +} + +/// The lion transform decryption function. +/// +/// The `key` must be 32 bytes, and the `message` >= 48. The message +/// is mutated to the decrypted message. +pub fn lion_transform_decrypt(message: &mut [u8], key: &[u8]) -> Result<(), OutFoxError> { + lion_transform(message, key, [3, 2, 1]) +} + +/// The core of the lion transform function, that takes a message and a key, +/// and applies the all-or-nothing transform. The key schedule represents the +/// values of the 3 subkeys used by the 3 phases of the transform. +/// +/// The `key` must be 32 bytes, and the `message` >= 48. +/// +/// Unless you know what you are doing use [lion_transform_encrypt] and +/// [lion_transform_decrypt] instead. +pub fn lion_transform( + message: &mut [u8], + key: &[u8], + key_schedule: [u64; 3], +) -> Result<(), OutFoxError> { + if key.len() != 32 { + return Err(OutFoxError::InvalidKeyLength); + } + + if message.len() < MIN_MESSAGE_LEN { + return Err(OutFoxError::InvalidMessageLength); + } + + // Stage 1: Use stream cipher with Nonce from left size, to xor to the right side + let mut derived_key = blake3::derive_key(&format!("{}{}", CONTEXT, key_schedule[0]), key); + let lion_stage_1_key = Key::from_slice(&derived_key); + let left_short_message = XNonce::from_slice(&message[..TAG_LEN]); + + let mut cipher = XChaCha20::new(lion_stage_1_key, left_short_message); + cipher.apply_keystream(&mut message[TAG_LEN..]); + + // Stage 2: Use HMAC of right size, and xor to the left side + derived_key = blake3::derive_key(&format!("{}{}", CONTEXT, key_schedule[1]), key); + + let mac = blake3::keyed_hash(&derived_key, &message[TAG_LEN..]); + let tag_to_xor = mac.as_bytes(); + + // Xor resulting HMAC into the left (short) message + for i in 0..TAG_LEN { + message[i] ^= tag_to_xor[i]; + } + + // Stage 3: (same as 1) + derived_key = blake3::derive_key(&format!("{}{}", CONTEXT, key_schedule[2]), key); + let lion_stage_3_key = Key::from_slice(&derived_key); + let left_short_message_final = XNonce::from_slice(&message[..TAG_LEN]); + + let mut cipher = XChaCha20::new(lion_stage_3_key, left_short_message_final); + + cipher.apply_keystream(&mut message[TAG_LEN..]); + + // clean up temp key + derived_key.zeroize(); + + Ok(()) +} diff --git a/nym-outfox/tests/unittests.rs b/nym-outfox/tests/unittests.rs new file mode 100644 index 0000000000..0123e55779 --- /dev/null +++ b/nym-outfox/tests/unittests.rs @@ -0,0 +1,142 @@ +extern crate nym_outfox; + +#[cfg(test)] +mod tests { + + use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; + use curve25519_dalek::scalar::Scalar; + use std::convert::TryInto; + + use nym_outfox::format::*; + use nym_outfox::lion::*; + + use std::iter::repeat_with; + + pub fn randombytes(n: usize) -> Vec { + repeat_with(|| fastrand::u8(..)).take(n).collect() + } + + #[test] + fn test_encode_decode() { + let mix_params = MixStageParameters { + routing_information_length_bytes: 32, + remaining_header_length_bytes: (32 + 16 + 32) * 4, + payload_length_bytes: 1024, // 1kb + }; + + let user_secret_bytes = randombytes(32); + let mix_secret_bytes = randombytes(32); + + let user_secret = Scalar::from_bytes_mod_order(user_secret_bytes.try_into().unwrap()); + let mix_secret = Scalar::from_bytes_mod_order(mix_secret_bytes.try_into().unwrap()); + let mix_public_key = (&ED25519_BASEPOINT_TABLE * &mix_secret).to_montgomery(); + + let routing = [0; 32]; + + let buffer = randombytes(mix_params.incoming_packet_length()); + + let mut new_buffer = buffer.clone(); + + let _ = mix_params + .encode_mix_layer( + &mut new_buffer[..], + &user_secret, + &mix_public_key, + &routing[..], + ) + .unwrap(); + + assert!(new_buffer[mix_params.payload_range()] != buffer[mix_params.payload_range()]); + assert!(new_buffer[mix_params.routing_data_range()] != routing[..]); + + let _ = mix_params + .decode_mix_layer(&mut new_buffer[..], &mix_secret) + .unwrap(); + + assert!(new_buffer[mix_params.payload_range()] == buffer[mix_params.payload_range()]); + assert!(new_buffer[mix_params.routing_data_range()] == routing[..]); + } + + #[test] + fn test_lion() { + let key = randombytes(32); + let message = randombytes(1024); + + let mut message_clone = message.clone(); + lion_transform(&mut message_clone[..], &key, [1, 2, 3]).unwrap(); + assert!(message_clone[..] != message[..]); + + let mut message_clone_2 = message.clone(); + lion_transform_encrypt(&mut message_clone_2, &key).unwrap(); + assert_eq!(message_clone_2, message_clone); + + lion_transform(&mut message_clone[..], &key[..], [3, 2, 1]).unwrap(); + assert!(message_clone[..] == message[..]); + } + + #[test] + fn test_packet_params() { + // Dummy keys -- we will use the same key for each layer + let user_secret_bytes = randombytes(32); + let mix_secret_bytes = randombytes(32); + + let user_secret = Scalar::from_bytes_mod_order(user_secret_bytes.try_into().unwrap()); + let mix_secret = Scalar::from_bytes_mod_order(mix_secret_bytes.try_into().unwrap()); + let mix_public_key = (&ED25519_BASEPOINT_TABLE * &mix_secret).to_montgomery(); + + let routing = [0; 32]; + + let mut params = MixCreationParameters::new(1025); + params.add_outer_layer(32); + params.add_outer_layer(32); + params.add_outer_layer(32); + + let mut buf = vec![0; params.total_packet_length()]; + + let (range0, layer_params0) = params.get_stage_params(0); + let _ = layer_params0 + .encode_mix_layer( + &mut buf[range0.clone()], + &user_secret, + &mix_public_key, + &routing[..], + ) + .unwrap(); + + let (range1, layer_params1) = params.get_stage_params(1); + let _ = layer_params1 + .encode_mix_layer( + &mut buf[range1.clone()], + &user_secret, + &mix_public_key, + &routing[..], + ) + .unwrap(); + + let (range2, layer_params2) = params.get_stage_params(2); + let _ = layer_params2 + .encode_mix_layer( + &mut buf[range2.clone()], + &user_secret, + &mix_public_key, + &routing[..], + ) + .unwrap(); + + assert!( + buf[params.total_packet_length() - 1025..params.total_packet_length()] != [0; 1025] + ); + + let _ = layer_params2 + .decode_mix_layer(&mut buf[range2], &mix_secret) + .unwrap(); + + let _ = layer_params1 + .decode_mix_layer(&mut buf[range1], &mix_secret) + .unwrap(); + + let _ = layer_params0 + .decode_mix_layer(&mut buf[range0], &mix_secret) + .unwrap(); + } +} diff --git a/nym-wallet/CHANGELOG.md b/nym-wallet/CHANGELOG.md index ae119b7749..8eccaf2471 100644 --- a/nym-wallet/CHANGELOG.md +++ b/nym-wallet/CHANGELOG.md @@ -1,10 +1,73 @@ # Changelog -## UNRELEASED +## [Unreleased] + +## [v1.1.13] (2023-03-15) + +- Wallet - in the vesting section separate the "Unlocked transferable tokens" into "Unlocked vesting tokens" and "Transferable rewards" for better clarity ([#3132]) +- Wallet - change the Bonding flow to include an additional step for the user to provide a unique signature when they bond their node ([#3109]) + +[#3132]: https://github.com/nymtech/nym/issues/3132 +[#3109]: https://github.com/nymtech/nym/issues/3109 + +## [v1.1.11] (2023-03-07) + +- Wallet: optional gas and memo fields ([#2222]) + +[#2222]: https://github.com/nymtech/nym/issues/2222 + +## [v1.1.10] (2023-02-20) + +- Wallet - Fix send address bug ([#3030]) +- Wallet - [Issue] the `Operator cost` field in the Playground does not allow values above 100.0000 ([#2998]) +- nym-wallet: fix auto-updated files ([#3043]) + +[#3030]: https://github.com/nymtech/nym/issues/3030 +[#2998]: https://github.com/nymtech/nym/issues/2998 +[#3043]: https://github.com/nymtech/nym/pull/3043 + +## [nym-wallet-v1.1.9](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.9) (2023-02-14) + +- Allow more flexibility for user when setting passwords ([#2993]) +- User feedback on weak passwords ([#2993]) +- User no longer has to copy mnemonic to continune account creation ([#2948]) +- Updated instructional steps for creating accounts with a password ([#2962]) + +[#2948]: https://github.com/nymtech/nym/issues/2948 +[#2993]: https://github.com/nymtech/nym/issues/2993 +[#2962]: https://github.com/nymtech/nym/issues/2962 + +## [nym-wallet-v1.1.8](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.8) (2023-01-24) + +- Fix delegations sorting ([#2885]) + +[#2885]: https://github.com/nymtech/nym/issues/2885 + +## [nym-wallet-v1.1.7](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.7) (2023-01-17) + +- link to the ng mixnet explorer for account info ([#2823]) +- Fix delegations sorting ([#2885]) (2023-01-23) + +[#2823]: https://github.com/nymtech/nym/pull/2823 + +## [nym-wallet-v1.1.6](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.6) (2023-01-10) + +- Fix param input layout **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2720 +- Add epoch info to unbond modal **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2718 +- Feat/2130 tables update rebase by @gala1234 https://github.com/nymtech/nym/pull/2742 +- pass wallet address as url param by @doums in https://github.com/nymtech/nym/pull/2780 + +## [nym-wallet-v1.1.5](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.5) (2022-12-22) + +This release contains the APY calculator feature. + +## [nym-wallet-v1.1.5](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.5) (2022-12-22) + +This release contains the APY calculator feature. ## [nym-wallet-v1.1.4](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.4) (2022-12-20) -This release contains a bugfix for hiding and displaying the mnemonic, and displays the Bity wallet address in the buy page, as well as better error handling. +This release contains a bugfix for hiding and displaying the mnemonic, and displays the Bity wallet address in the buy page, as well as better error handling. - wallet: present some cases of the more technical errors (abci, ..) in a more human readable form. - Bug fix/hide display mnemonic by @fmtabbara in https://github.com/nymtech/nym/pull/1874 @@ -24,13 +87,12 @@ This release contains a bugfix for hiding and displaying the mnemonic, and displ - wallet: compatibility with nym-binaries-1.1.1 - ## [nym-wallet-v1.1.0](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.0) (2022-11-09) - wallet: Add window for showing logs for when the terminal is not available. - wallet: Rework of rewarding ([#1472]) - wallet: Highlight delegations on an unbonded node to notify the delegator to undelegate stake -- wallet: Group delegations on unbonded nodes at the top of the list +- wallet: Group delegations on unbonded nodes at the top of the list - wallet: Simplified delegation screen when user has no delegations - wallet: Update "create password" howto guide - wallet: Allow setting of operating cost when node is bonded @@ -44,13 +106,13 @@ This release contains a bugfix for hiding and displaying the mnemonic, and displ ## [nym-wallet-v1.0.9](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.0.8) (2022-09-08) -- wallet: change default `nymd` URL to https://rpc.nymtech.net +- wallet: change default `nyxd` URL to https://rpc.nymtech.net ## [nym-wallet-v1.0.8](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.0.8) (2022-08-11) - wallet: new bonding flow and screen for bonded node - wallet: compound and redeem functionalities for operator rewards -- wallet: a few minor touch ups and bug fixes +- wallet: a few minor touch ups and bug fixes ## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11) @@ -59,7 +121,6 @@ This release contains a bugfix for hiding and displaying the mnemonic, and displ [#1388]: https://github.com/nymtech/nym/pull/1388 - ## [nym-wallet-v1.0.6](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.6) (2022-06-21) - wallet: undelegating now uses either the mixnet or vesting contract, or both, depending on how delegations were made @@ -120,7 +181,7 @@ This release contains a bugfix for hiding and displaying the mnemonic, and displ - Type safe bounds [\#1179](https://github.com/nymtech/nym/pull/1179) ([durch](https://github.com/durch)) - Fix delegation paging [\#1174](https://github.com/nymtech/nym/pull/1174) ([durch](https://github.com/durch)) - Update binaries to rc version [\#1172](https://github.com/nymtech/nym/pull/1172) ([tommyv1987](https://github.com/tommyv1987)) -- Bump ansi-regex from 4.1.0 to 4.1.1 in /docker/typescript\_client/upload\_contract [\#1171](https://github.com/nymtech/nym/pull/1171) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Bump ansi-regex from 4.1.0 to 4.1.1 in /docker/typescript_client/upload_contract [\#1171](https://github.com/nymtech/nym/pull/1171) ([dependabot[bot]](https://github.com/apps/dependabot)) ## [nym-wallet-v1.0.2](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.2) (2022-04-05) @@ -136,12 +197,12 @@ This release contains a bugfix for hiding and displaying the mnemonic, and displ - wallet: add test for decrypting stored wallet file [\#1189](https://github.com/nymtech/nym/pull/1189) ([octol](https://github.com/octol)) - Fix clippy warnings [\#1188](https://github.com/nymtech/nym/pull/1188) ([octol](https://github.com/octol)) - Password for wallet with routes [\#1187](https://github.com/nymtech/nym/pull/1187) ([mmsinclair](https://github.com/mmsinclair)) -- wallet: add validate\_mnemonic [\#1186](https://github.com/nymtech/nym/pull/1186) ([octol](https://github.com/octol)) +- wallet: add validate_mnemonic [\#1186](https://github.com/nymtech/nym/pull/1186) ([octol](https://github.com/octol)) - wallet: support removing accounts from the wallet file [\#1185](https://github.com/nymtech/nym/pull/1185) ([octol](https://github.com/octol)) - Feature/adding discord [\#1184](https://github.com/nymtech/nym/pull/1184) ([gala1234](https://github.com/gala1234)) - wallet: config backend for validator selection [\#1183](https://github.com/nymtech/nym/pull/1183) ([octol](https://github.com/octol)) - Add storybook to wallet [\#1178](https://github.com/nymtech/nym/pull/1178) ([mmsinclair](https://github.com/mmsinclair)) -- wallet: connection test nymd and api urls independently [\#1170](https://github.com/nymtech/nym/pull/1170) ([octol](https://github.com/octol)) +- wallet: connection test nyxd and api urls independently [\#1170](https://github.com/nymtech/nym/pull/1170) ([octol](https://github.com/octol)) - wallet: wire up account storage [\#1153](https://github.com/nymtech/nym/pull/1153) ([octol](https://github.com/octol)) - Feature/signature on deposit [\#1151](https://github.com/nymtech/nym/pull/1151) ([neacsu](https://github.com/neacsu)) @@ -175,7 +236,7 @@ This release contains a bugfix for hiding and displaying the mnemonic, and displ **Closed issues:** - Rewarded set inclusion probability API endpoint [\#1037](https://github.com/nymtech/nym/issues/1037) -- Update cw-storage-plus to 0.11 [\#1032](https://github.com/nymtech/nym/issues/1032) +- Update cw-storage-plus to 0.11 [\#1032](https://github.com/nymtech/nym/issues/1032) - Change `u128` fields in `RewardEstimationResponse` to `u64` [\#1029](https://github.com/nymtech/nym/issues/1029) - Test out the mainnet Gravity Bridge [\#1006](https://github.com/nymtech/nym/issues/1006) - Add vesting contract interface to nym-wallet [\#959](https://github.com/nymtech/nym/issues/959) @@ -197,7 +258,7 @@ This release contains a bugfix for hiding and displaying the mnemonic, and displ - Bump follow-redirects from 1.14.1 to 1.14.7 in /clients/native/examples/js-examples/websocket [\#1079](https://github.com/nymtech/nym/pull/1079) ([dependabot[bot]](https://github.com/apps/dependabot)) - Bump nanoid from 3.1.23 to 3.2.0 in /clients/native/examples/js-examples/websocket [\#1078](https://github.com/nymtech/nym/pull/1078) ([dependabot[bot]](https://github.com/apps/dependabot)) - Setup basic test for mixnode stats reporting [\#1077](https://github.com/nymtech/nym/pull/1077) ([octol](https://github.com/octol)) -- Make wallet\_address mandatory for mixnode init [\#1076](https://github.com/nymtech/nym/pull/1076) ([octol](https://github.com/octol)) +- Make wallet_address mandatory for mixnode init [\#1076](https://github.com/nymtech/nym/pull/1076) ([octol](https://github.com/octol)) - Tidy nym-mixnode module visibility [\#1075](https://github.com/nymtech/nym/pull/1075) ([octol](https://github.com/octol)) - Feature/wallet login with password [\#1074](https://github.com/nymtech/nym/pull/1074) ([fmtabbara](https://github.com/fmtabbara)) - Add trait to mock client dependency in DelayForwarder [\#1073](https://github.com/nymtech/nym/pull/1073) ([octol](https://github.com/octol)) @@ -206,7 +267,7 @@ This release contains a bugfix for hiding and displaying the mnemonic, and displ - Add @octol as codeowner [\#1068](https://github.com/nymtech/nym/pull/1068) ([octol](https://github.com/octol)) - set-up inclusion probability [\#1067](https://github.com/nymtech/nym/pull/1067) ([fmtabbara](https://github.com/fmtabbara)) - Feature/wasm client [\#1066](https://github.com/nymtech/nym/pull/1066) ([neacsu](https://github.com/neacsu)) -- Changed bech32\_prefix from punk to nymt [\#1064](https://github.com/nymtech/nym/pull/1064) ([jstuczyn](https://github.com/jstuczyn)) +- Changed bech32_prefix from punk to nymt [\#1064](https://github.com/nymtech/nym/pull/1064) ([jstuczyn](https://github.com/jstuczyn)) - Bump nanoid from 3.1.30 to 3.2.0 in /testnet-faucet [\#1063](https://github.com/nymtech/nym/pull/1063) ([dependabot[bot]](https://github.com/apps/dependabot)) - Bump nanoid from 3.1.30 to 3.2.0 in /nym-wallet [\#1062](https://github.com/nymtech/nym/pull/1062) ([dependabot[bot]](https://github.com/apps/dependabot)) - Rework vesting contract storage [\#1061](https://github.com/nymtech/nym/pull/1061) ([durch](https://github.com/durch)) @@ -214,7 +275,7 @@ This release contains a bugfix for hiding and displaying the mnemonic, and displ - fix: make explorer footer year dynamic [\#1059](https://github.com/nymtech/nym/pull/1059) ([martinyung](https://github.com/martinyung)) - Add mnemonic just on creation, to display it [\#1057](https://github.com/nymtech/nym/pull/1057) ([neacsu](https://github.com/neacsu)) - Network Explorer: updates to API and UI to show the active set [\#1056](https://github.com/nymtech/nym/pull/1056) ([mmsinclair](https://github.com/mmsinclair)) -- Made contract addresses for query NymdClient construction optional [\#1055](https://github.com/nymtech/nym/pull/1055) ([jstuczyn](https://github.com/jstuczyn)) +- Made contract addresses for query NyxdClient construction optional [\#1055](https://github.com/nymtech/nym/pull/1055) ([jstuczyn](https://github.com/jstuczyn)) - Introduced RPC query for total token supply [\#1053](https://github.com/nymtech/nym/pull/1053) ([jstuczyn](https://github.com/jstuczyn)) - Feature/tokio console [\#1052](https://github.com/nymtech/nym/pull/1052) ([durch](https://github.com/durch)) - Implemented beta clippy lint recommendations [\#1051](https://github.com/nymtech/nym/pull/1051) ([jstuczyn](https://github.com/jstuczyn)) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 41db8105db..d88f92b3ea 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -20,7 +20,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.5", + "generic-array", ] [[package]] @@ -32,7 +32,7 @@ dependencies = [ "cfg-if", "cipher", "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -50,43 +50,45 @@ dependencies = [ ] [[package]] -name = "aho-corasick" -version = "0.7.18" +name = "ahash" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.8", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "alloc-no-stdlib" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "argon2" @@ -107,14 +109,14 @@ checksum = "a27e27b63e4a34caee411ade944981136fdfa535522dc9944d6700196cbd899f" dependencies = [ "base64ct", "blake2", - "password-hash 0.4.1", + "password-hash 0.4.2", ] [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", @@ -142,7 +144,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.2", + "system-deps 6.0.3", ] [[package]] @@ -167,7 +169,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -195,9 +197,15 @@ checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "base64ct" @@ -216,7 +224,7 @@ dependencies = [ "k256", "once_cell", "pbkdf2", - "rand_core 0.6.3", + "rand_core 0.6.4", "ripemd160", "sha2 0.9.9", "subtle", @@ -262,11 +270,11 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -275,44 +283,23 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding 0.1.5", - "byte-tools", - "byteorder", - "generic-array 0.12.4", -] - [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding 0.2.1", - "generic-array 0.14.5", + "block-padding", + "generic-array", ] [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", + "generic-array", ] [[package]] @@ -330,7 +317,7 @@ dependencies = [ "ff", "group", "pairing", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -348,9 +335,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.2" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -367,30 +354,25 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.17" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "b7f0778972c64420fdedc63f09919c8a88bda7b25135357fd25a5d9f3257e832" dependencies = [ "memchr", + "serde", ] [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "bytemuck" -version = "1.8.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead" +checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" [[package]] name = "byteorder" @@ -400,15 +382,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cairo-rs" -version = "0.15.6" +version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b14c80d8d1a02fa6d914b9d1afeeca9bc34257f8300d9696e1e331ae114223" +checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" dependencies = [ "bitflags", "cairo-sys-rs", @@ -425,14 +407,14 @@ checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.2", + "system-deps 6.0.3", ] [[package]] name = "cargo_toml" -version = "0.13.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa0e3586af56b3bfa51fca452bd56e8dbbbd5d8d81cbf0b7e4e35b695b537eb8" +checksum = "497049e9477329f8f6a559972ee42e117487d01d1e8c2cc9f836ea6fa23a9e1a" dependencies = [ "serde", "toml", @@ -440,9 +422,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] @@ -469,16 +451,16 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" dependencies = [ - "smallvec 1.8.0", + "smallvec 1.10.0", ] [[package]] name = "cfg-expr" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e068cb2806bbc15b439846dc16c5f89f8599f2c3e4d73d4449d38f9b2f0b6c5" +checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" dependencies = [ - "smallvec 1.8.0", + "smallvec 1.10.0", ] [[package]] @@ -487,52 +469,56 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "time 0.1.43", - "winapi", -] - [[package]] name = "cipher" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.5", + "generic-array", ] [[package]] name = "clap" -version = "3.2.20" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd" +checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" dependencies = [ - "atty", "bitflags", "clap_derive", "clap_lex", - "indexmap", + "is-terminal", "once_cell", "strsim", "termcolor", - "textwrap", +] + +[[package]] +name = "clap_complete" +version = "4.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501ff0a401473ea1d4c3b125ff95506b62c5bc5768d818634195fbb7c4ad5ff4" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_complete_fig" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63a06158a72dbb088f864887b4409fd22600ba379f3cee3040f842234cc5c2d0" +dependencies = [ + "clap", + "clap_complete", ] [[package]] name = "clap_derive" -version = "3.2.18" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", @@ -541,9 +527,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" dependencies = [ "os_str_bytes", ] @@ -559,9 +545,9 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" dependencies = [ "bitflags", "block", @@ -588,39 +574,6 @@ dependencies = [ "objc", ] -[[package]] -name = "coconut-bandwidth-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-dkg-common" -version = "0.1.0" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "cw-utils", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-interface" -version = "0.1.0" -dependencies = [ - "bs58", - "getset", - "nymcoconut", - "serde", - "thiserror", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -659,36 +612,12 @@ dependencies = [ "memchr", ] -[[package]] -name = "config" -version = "0.1.0" -dependencies = [ - "cfg-if", - "handlebars", - "log", - "network-defaults", - "serde", - "toml", - "url", -] - [[package]] name = "const-oid" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" -[[package]] -name = "contracts-common" -version = "0.1.0" -dependencies = [ - "bs58", - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "convert_case" version = "0.4.0" @@ -713,9 +642,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "core-graphics" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ "bitflags", "core-foundation", @@ -755,11 +684,11 @@ dependencies = [ "cosmos-sdk-proto", "ecdsa", "eyre", - "getrandom 0.2.5", + "getrandom 0.2.8", "k256", "prost", "prost-types", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", "serde_json", "subtle-encoding", @@ -777,15 +706,15 @@ dependencies = [ "digest 0.9.0", "ed25519-zebra", "k256", - "rand_core 0.6.3", + "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.0.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" +checksum = "d5abeeb891e6d0098402e4d3d042f90451db52651d2fe14b170e69a1dd3e4115" dependencies = [ "syn", ] @@ -796,7 +725,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" dependencies = [ - "base64", + "base64 0.13.1", "cosmwasm-crypto", "cosmwasm-derive", "forward_ref", @@ -837,12 +766,11 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -857,19 +785,19 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ - "generic-array 0.14.5", - "rand_core 0.6.3", + "generic-array", + "rand_core 0.6.4", "subtle", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.5", + "generic-array", "typenum", ] @@ -879,7 +807,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.5", + "generic-array", "subtle", ] @@ -896,7 +824,7 @@ dependencies = [ "phf 0.8.0", "proc-macro2", "quote", - "smallvec 1.8.0", + "smallvec 1.10.0", "syn", ] @@ -921,9 +849,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.21" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", "syn", @@ -1034,9 +962,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ "darling_core", "darling_macro", @@ -1044,9 +972,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", @@ -1058,9 +986,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", @@ -1089,31 +1017,22 @@ dependencies = [ "syn", ] -[[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.5", + "generic-array", ] [[package]] name = "digest" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", "subtle", ] @@ -1139,9 +1058,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -1166,31 +1085,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] -name = "dkg" -version = "0.1.0" -dependencies = [ - "bitvec", - "bls12_381", - "bs58", - "ff", - "group", - "lazy_static", - "pemstore", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.3", - "serde", - "serde_derive", - "sha2 0.9.9", - "thiserror", - "zeroize", -] - -[[package]] -name = "dotenv" -version = "0.15.0" +name = "dotenvy" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" [[package]] name = "dtoa" @@ -1215,9 +1113,9 @@ checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" [[package]] name = "dyn-clone" -version = "1.0.4" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" +checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" [[package]] name = "ecdsa" @@ -1233,9 +1131,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ "signature", ] @@ -1254,24 +1152,24 @@ dependencies = [ [[package]] name = "ed25519-zebra" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ "curve25519-dalek", + "hashbrown", "hex", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", "sha2 0.9.9", - "thiserror", "zeroize", ] [[package]] name = "either" -version = "1.6.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" @@ -1283,9 +1181,9 @@ dependencies = [ "crypto-bigint", "der", "ff", - "generic-array 0.14.5", + "generic-array", "group", - "rand_core 0.6.3", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -1299,27 +1197,27 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if", ] [[package]] name = "enum-iterator" -version = "0.7.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401" dependencies = [ "enum-iterator-derive", ] [[package]] name = "enum-iterator-derive" -version = "0.7.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce" dependencies = [ "proc-macro2", "quote", @@ -1340,34 +1238,41 @@ dependencies = [ ] [[package]] -name = "execute" -version = "0.1.0" +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ - "quote", - "syn", + "errno-dragonfly", + "libc", + "winapi", +] + +[[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 = "eyre" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9289ed2c0440a6536e65119725cf91fc2c6b5e513bfd2e36e1134d7cca6ca12f" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" dependencies = [ "indenter", "once_cell", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -1384,11 +1289,11 @@ dependencies = [ [[package]] name = "ff" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2958d04124b9f27f175eaeb9a9f383d026098aa837eadd8ba22c11f13a05b9e" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -1404,26 +1309,24 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.15" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" dependencies = [ "cfg-if", "libc", "redox_syscall", - "winapi", + "windows-sys 0.45.0", ] [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ - "cfg-if", "crc32fast", - "libc", - "miniz_oxide 0.4.4", + "miniz_oxide", ] [[package]] @@ -1459,11 +1362,10 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] @@ -1497,9 +1399,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -1512,9 +1414,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -1522,15 +1424,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -1539,15 +1441,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -1556,21 +1458,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -1611,9 +1513,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.15.6" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8750501d75f318c2ec0314701bc8403901303210def80bafd13f6b6059a3f45" +checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" dependencies = [ "bitflags", "gdk-pixbuf-sys", @@ -1624,15 +1526,15 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.15.1" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413424d9818621fa3cfc8a3a915cdb89a7c3c507d56761b4ec83a9a98e587171" +checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" dependencies = [ "gio-sys", "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.2", + "system-deps 6.0.3", ] [[package]] @@ -1649,7 +1551,7 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 6.0.2", + "system-deps 6.0.3", ] [[package]] @@ -1661,37 +1563,28 @@ dependencies = [ "gdk-sys", "glib-sys", "libc", - "system-deps 6.0.2", + "system-deps 6.0.3", "x11", ] [[package]] name = "generator" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" +checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e" dependencies = [ "cc", "libc", "log", "rustversion", - "winapi", + "windows 0.39.0", ] [[package]] name = "generic-array" -version = "0.12.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -1710,14 +1603,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.5" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1739,15 +1632,15 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ - "opaque-debug 0.3.0", + "opaque-debug", "polyval", ] [[package]] name = "gio" -version = "0.15.6" +version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96efd8a1c00d890f6b45671916e165b5e43ccec61957d443aff6d7e44f62d348" +checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" dependencies = [ "bitflags", "futures-channel", @@ -1762,22 +1655,22 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.15.6" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0fa5052773f5a56b8ae47dab09d040f5d9ce1311f4f99006e16e9a08269296" +checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.2", + "system-deps 6.0.3", "winapi", ] [[package]] name = "git2" -version = "0.15.0" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1" +checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" dependencies = [ "bitflags", "libc", @@ -1788,9 +1681,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.15.6" +version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa570813c504bdf7539a9400180c2dd4b789a819556fb86da7226d7d1b037b49" +checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" dependencies = [ "bitflags", "futures-channel", @@ -1802,18 +1695,18 @@ dependencies = [ "gobject-sys", "libc", "once_cell", - "smallvec 1.8.0", + "smallvec 1.10.0", "thiserror", ] [[package]] name = "glib-macros" -version = "0.15.6" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41bfd8d227dead0829ac142454e97531b93f576d0805d779c42bfd799c65c572" +checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" dependencies = [ "anyhow", - "heck 0.4.0", + "heck 0.4.1", "proc-macro-crate", "proc-macro-error", "proc-macro2", @@ -1823,25 +1716,25 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.15.6" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4366377bd56697de8aaee24e673c575d2694d72e7756324ded2b0428829a7b8" +checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" dependencies = [ "libc", - "system-deps 6.0.2", + "system-deps 6.0.3", ] [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ "aho-corasick", "bstr", @@ -1852,13 +1745,13 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.15.5" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6859463843c20cf3837e3a9069b6ab2051aeeadf4c899d33344f4aea83189a" +checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" dependencies = [ "glib-sys", "libc", - "system-deps 6.0.2", + "system-deps 6.0.3", ] [[package]] @@ -1869,15 +1762,15 @@ checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", "ff", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] [[package]] name = "gtk" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2d1326b36af927fe46ae2f89a8fec38c6f0d279ebc5ef07ffeeabb70300bfc" +checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" dependencies = [ "atk", "bitflags", @@ -1911,7 +1804,7 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps 6.0.2", + "system-deps 6.0.3", ] [[package]] @@ -1930,9 +1823,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.11" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -1963,24 +1856,27 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "headers" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64", + "base64 0.13.1", "bitflags", "bytes", "headers-core", "http", "httpdate", "mime", - "sha-1 0.10.0", + "sha1", ] [[package]] @@ -2003,9 +1899,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -2016,6 +1912,21 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -2040,9 +1951,9 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" +checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" dependencies = [ "log", "mac", @@ -2060,14 +1971,14 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.1", + "itoa 1.0.5", ] [[package]] name = "http-body" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", @@ -2082,9 +1993,9 @@ checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" [[package]] name = "httparse" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -2119,9 +2030,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.17" +version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ "bytes", "futures-channel", @@ -2132,7 +2043,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.1", + "itoa 1.0.5", "pin-project-lite", "socket2", "tokio", @@ -2220,11 +2131,10 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.18" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ - "crossbeam-utils", "globset", "lazy_static", "log", @@ -2238,9 +2148,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.3" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" dependencies = [ "bytemuck", "byteorder", @@ -2257,9 +2167,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.0" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg 1.1.0", "hashbrown", @@ -2285,16 +2195,38 @@ dependencies = [ ] [[package]] -name = "ipnet" -version = "2.3.1" +name = "io-lifetimes" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" +dependencies = [ + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "ipnet" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" + +[[package]] +name = "is-terminal" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -2307,9 +2239,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "javascriptcore-rs" @@ -2365,18 +2297,18 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] [[package]] name = "json-patch" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f995a3c8f2bc3dd52a18a583e90f9ec109c047fa1603a853e46bcda14d2e279d" +checksum = "eb3fa5a61630976fc4c353c70297f2e93f1930e3ccee574d59d618ccbd5154ce" dependencies = [ "serde", "serde_json", @@ -2399,9 +2331,12 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] [[package]] name = "kuchiki" @@ -2423,15 +2358,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.134" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libgit2-sys" -version = "0.14.0+1.5.0" +version = "0.13.5+1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a00859c70c8a4f7218e6d1cc32875c4b55f6799445b842b0d8ed5e4c3d959b" +checksum = "51e5ea06c26926f1002dd553fded6cfcdc9784c1f60feeb58368b4d9b07b6dba" dependencies = [ "cc", "libc", @@ -2461,37 +2396,36 @@ dependencies = [ ] [[package]] -name = "lock_api" -version = "0.4.6" +name = "linux-raw-sys" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ + "autocfg 1.1.0", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", "serde", ] -[[package]] -name = "logging" -version = "0.1.0" -dependencies = [ - "log", - "pretty_env_logger", -] - [[package]] name = "loom" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" dependencies = [ "cfg-if", "generator", @@ -2517,12 +2451,6 @@ dependencies = [ "libc", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "markup5ever" version = "0.10.1" @@ -2548,9 +2476,9 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "maybe-uninit" @@ -2560,9 +2488,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" @@ -2587,71 +2515,30 @@ checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg 1.1.0", -] - -[[package]] -name = "miniz_oxide" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", -] - -[[package]] -name = "mixnet-contract-common" -version = "0.1.0" -dependencies = [ - "bs58", - "contracts-common", - "cosmwasm-std", - "humantime-serde", - "log", - "schemars", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "time 0.3.7", - "ts-rs", -] - -[[package]] -name = "multisig-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "cw3", - "cw3-fixed-multisig", - "cw4", - "schemars", - "serde", + "windows-sys 0.42.0", ] [[package]] name = "native-tls" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -2680,9 +2567,9 @@ dependencies = [ [[package]] name = "ndk-context" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5cc68637e21fe8f077f6a1c9e0b9ca495bb74895226b476310f613325884" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] name = "ndk-sys" @@ -2693,19 +2580,6 @@ dependencies = [ "jni-sys", ] -[[package]] -name = "network-defaults" -version = "0.1.0" -dependencies = [ - "cfg-if", - "dotenv", - "hex-literal", - "once_cell", - "serde", - "thiserror", - "url", -] - [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -2718,6 +2592,25 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom8" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" +dependencies = [ + "memchr", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -2731,9 +2624,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg 1.1.0", "num-traits", @@ -2752,37 +2645,37 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg 1.1.0", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "num_enum" -version = "0.5.6" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" +checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.6" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" +checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2792,25 +2685,201 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ "libc", ] +[[package]] +name = "nym-api-requests" +version = "0.1.0" +dependencies = [ + "bs58", + "cosmrs", + "cosmwasm-std", + "getset", + "nym-coconut-interface", + "nym-mixnet-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-bin-common" +version = "0.2.0" +dependencies = [ + "clap", + "clap_complete", + "clap_complete_fig", + "log", + "pretty_env_logger", + "semver 0.11.0", + "vergen", +] + +[[package]] +name = "nym-coconut-bandwidth-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-dkg-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", + "nym-multisig-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-coconut-interface" +version = "0.1.0" +dependencies = [ + "bs58", + "getset", + "nymcoconut", + "serde", + "thiserror", +] + +[[package]] +name = "nym-config" +version = "0.1.0" +dependencies = [ + "cfg-if", + "handlebars", + "log", + "nym-network-defaults", + "serde", + "toml", + "url", +] + +[[package]] +name = "nym-contracts-common" +version = "0.2.0" +dependencies = [ + "bs58", + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-dkg" +version = "0.1.0" +dependencies = [ + "bitvec", + "bls12_381", + "bs58", + "ff", + "group", + "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-execute" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "nym-group-contract-common" +version = "0.1.0" +dependencies = [ + "cw4", + "schemars", + "serde", +] + +[[package]] +name = "nym-mixnet-contract-common" +version = "0.2.0" +dependencies = [ + "bs58", + "cosmwasm-std", + "humantime-serde", + "log", + "nym-contracts-common", + "schemars", + "serde", + "serde_json", + "serde_repr", + "thiserror", + "time", + "ts-rs", +] + +[[package]] +name = "nym-multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw3-fixed-multisig", + "cw4", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-network-defaults" +version = "0.1.0" +dependencies = [ + "cfg-if", + "dotenvy", + "hex-literal", + "once_cell", + "serde", + "thiserror", + "url", +] + +[[package]] +name = "nym-pemstore" +version = "0.2.0" +dependencies = [ + "pem", +] + [[package]] name = "nym-types" version = "1.0.0" dependencies = [ - "coconut-interface", - "config", "cosmrs", "cosmwasm-std", "eyre", "itertools", "log", - "mixnet-contract-common", + "nym-coconut-interface", + "nym-config", + "nym-mixnet-contract-common", + "nym-vesting-contract", + "nym-vesting-contract-common", "reqwest", "schemars", "serde", @@ -2820,8 +2889,34 @@ dependencies = [ "ts-rs", "url", "validator-client", - "vesting-contract", - "vesting-contract-common", +] + +[[package]] +name = "nym-vesting-contract" +version = "1.2.0-pre.1" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "cw2", + "nym-contracts-common", + "nym-mixnet-contract-common", + "nym-vesting-contract-common", + "schemars", + "semver 1.0.16", + "serde", + "thiserror", + "vergen", +] + +[[package]] +name = "nym-vesting-contract-common" +version = "0.2.0" +dependencies = [ + "cosmwasm-std", + "nym-contracts-common", + "nym-mixnet-contract-common", + "schemars", + "serde", ] [[package]] @@ -2831,11 +2926,11 @@ dependencies = [ "aes-gcm", "anyhow", "argon2 0.4.0", - "base64", + "base64 0.13.1", "bip39", "clap", "log", - "logging", + "nym-bin-common", "pretty_env_logger", "serde_json", ] @@ -2844,47 +2939,48 @@ dependencies = [ name = "nym-wallet-types" version = "1.0.0" dependencies = [ - "config", "cosmrs", "cosmwasm-std", "hex-literal", - "mixnet-contract-common", - "network-defaults", + "nym-config", + "nym-mixnet-contract-common", + "nym-network-defaults", "nym-types", + "nym-vesting-contract", + "nym-vesting-contract-common", "serde", "serde_json", "strum", "ts-rs", "validator-client", - "vesting-contract", - "vesting-contract-common", ] [[package]] name = "nym_wallet" -version = "1.1.2" +version = "1.1.11" dependencies = [ "aes-gcm", "argon2 0.3.4", - "base64", + "base64 0.13.1", "bip39", "cfg-if", - "chrono", - "coconut-interface", "colored 2.0.0", - "config", "cosmrs", "cosmwasm-std", "dirs", - "dotenv", + "dotenvy", "eyre", "fern", "futures", "itertools", "k256", "log", - "mixnet-contract-common", + "nym-coconut-interface", + "nym-config", + "nym-mixnet-contract-common", "nym-types", + "nym-vesting-contract", + "nym-vesting-contract-common", "nym-wallet-types", "once_cell", "pretty_env_logger", @@ -2901,13 +2997,12 @@ dependencies = [ "tempfile", "tendermint-rpc", "thiserror", + "time", "tokio", "toml", "ts-rs", "url", "validator-client", - "vesting-contract", - "vesting-contract-common", "zeroize", ] @@ -2918,12 +3013,12 @@ dependencies = [ "bls12_381", "bs58", "digest 0.9.0", - "dkg", "ff", - "getrandom 0.2.5", + "getrandom 0.2.8", "group", "itertools", - "pemstore", + "nym-dkg", + "nym-pemstore", "rand 0.8.5", "serde", "serde_derive", @@ -2972,15 +3067,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "opaque-debug" @@ -2990,28 +3079,40 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "open" -version = "3.0.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23a407004a1033f53e93f9b45580d14de23928faad187384f891507c9b0c045" +checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" dependencies = [ "pathdiff", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "openssl" -version = "0.10.38" +version = "0.10.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", + "openssl-macros", "openssl-sys", ] +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -3020,9 +3121,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.72" +version = "0.9.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" dependencies = [ "autocfg 1.1.0", "cc", @@ -3033,9 +3134,15 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.0.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pairing" @@ -3048,9 +3155,9 @@ dependencies = [ [[package]] name = "pango" -version = "0.15.6" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c7420fc01a390ec200da7395b64d705f5d82fe03e5d0708aee422c46538be7" +checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" dependencies = [ "bitflags", "glib", @@ -3061,25 +3168,14 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.15.1" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7022c2fb88cd2d9d55e1a708a8c53a3ae8678234c4a54bf623400aeb7f31fac2" +checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.0.2", -] - -[[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.5", + "system-deps 6.0.3", ] [[package]] @@ -3089,34 +3185,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", + "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec 1.8.0", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec 1.8.0", - "windows-sys", + "smallvec 1.10.0", + "windows-sys 0.45.0", ] [[package]] @@ -3126,26 +3208,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" dependencies = [ "base64ct", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] [[package]] name = "password-hash" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e029e94abc8fb0065241c308f1ac6bc8d20f450e8f7c5f0b25cd9b8d526ba294" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] [[package]] name = "paste" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "pathdiff" @@ -3195,18 +3277,11 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" dependencies = [ - "base64", + "base64 0.13.1", "once_cell", "regex", ] -[[package]] -name = "pemstore" -version = "0.1.0" -dependencies = [ - "pem", -] - [[package]] name = "percent-encoding" version = "2.2.0" @@ -3215,18 +3290,19 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" dependencies = [ + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.1.0" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69" dependencies = [ "pest", "pest_generator", @@ -3234,9 +3310,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202" dependencies = [ "pest", "pest_meta", @@ -3247,13 +3323,13 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616" dependencies = [ - "maplit", + "once_cell", "pest", - "sha-1 0.8.2", + "sha2 0.10.6", ] [[package]] @@ -3356,18 +3432,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -3376,9 +3452,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -3399,34 +3475,34 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "plist" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" +checksum = "5329b8f106a176ab0dce4aae5da86bfcb139bb74fb00882859e03745011f3635" dependencies = [ - "base64", + "base64 0.13.1", "indexmap", "line-wrap", + "quick-xml", "serde", - "time 0.3.7", - "xml-rs", + "time", ] [[package]] name = "png" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" dependencies = [ "bitflags", "crc32fast", "flate2", - "miniz_oxide 0.5.4", + "miniz_oxide", ] [[package]] @@ -3437,15 +3513,15 @@ checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug", "universal-hash", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "precomputed-hash" @@ -3465,12 +3541,12 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" dependencies = [ - "thiserror", - "toml", + "once_cell", + "toml_edit", ] [[package]] @@ -3499,24 +3575,24 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.19" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc03e116981ff7d8da8e5c220e374587b98d294af7ba7dd7fda761158f00086f" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" dependencies = [ "bytes", "prost-derive", @@ -3558,10 +3634,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] -name = "quote" -version = "1.0.15" +name = "quick-xml" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -3613,7 +3698,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -3643,7 +3728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -3672,11 +3757,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.5", + "getrandom 0.2.8", ] [[package]] @@ -3779,28 +3864,29 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.5", + "getrandom 0.2.8", "redox_syscall", + "thiserror", ] [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -3818,9 +3904,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -3833,11 +3919,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.9" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ - "base64", + "base64 0.21.0", "bytes", "encoding_rs", "futures-core", @@ -3849,10 +3935,10 @@ dependencies = [ "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log", "mime", "native-tls", + "once_cell", "percent-encoding", "pin-project-lite", "serde", @@ -3860,6 +3946,7 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-native-tls", + "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -3925,7 +4012,7 @@ checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -3943,7 +4030,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.6", + "semver 1.0.16", +] + +[[package]] +name = "rustix" +version = "0.36.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", ] [[package]] @@ -3952,7 +4053,7 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64", + "base64 0.13.1", "log", "ring", "sct", @@ -3973,15 +4074,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "safemem" @@ -4000,19 +4101,18 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "schemars" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b5a3c80cea1ab61f4260238409510e814e38b4b563c06044edf91e7dc070e3" +checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307" dependencies = [ "dyn-clone", "indexmap", @@ -4023,9 +4123,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b" +checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" dependencies = [ "proc-macro2", "quote", @@ -4035,9 +4135,9 @@ dependencies = [ [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -4062,7 +4162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", - "generic-array 0.14.5", + "generic-array", "pkcs8", "subtle", "zeroize", @@ -4070,9 +4170,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.6.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", @@ -4083,9 +4183,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -4107,7 +4207,7 @@ dependencies = [ "phf_codegen", "precomputed-hash", "servo_arc", - "smallvec 1.8.0", + "smallvec 1.10.0", "thin-slice", ] @@ -4122,9 +4222,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.6" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" dependencies = [ "serde", ] @@ -4140,9 +4240,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.150" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] @@ -4158,18 +4258,18 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.5" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.150" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -4178,9 +4278,9 @@ dependencies = [ [[package]] name = "serde_derive_internals" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", @@ -4189,20 +4289,20 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ - "itoa 1.0.1", + "itoa 1.0.5", "ryu", "serde", ] [[package]] name = "serde_repr" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" dependencies = [ "proc-macro2", "quote", @@ -4216,27 +4316,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.1", + "itoa 1.0.5", "ryu", "serde", ] [[package]] name = "serde_with" -version = "1.12.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1e6ec4d8950e5b1e894eac0d360742f3b1407a6078a604a731c4b3f49cefbc" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" dependencies = [ - "rustversion", "serde", "serde_with_macros", ] [[package]] name = "serde_with_macros" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ "darling", "proc-macro2", @@ -4277,26 +4376,14 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.8.2" +name = "sha1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -4309,18 +4396,18 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -4332,7 +4419,7 @@ dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", "keccak", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -4346,34 +4433,37 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "signature" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] name = "siphasher" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "slab" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg 1.1.0", +] [[package]] name = "smallvec" @@ -4386,15 +4476,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -4467,13 +4557,13 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_cache" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33994d0838dc2d152d17a62adf608a869b5e846b65b389af7f3dbc1de45c5b26" +checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" dependencies = [ - "lazy_static", "new_debug_unreachable", - "parking_lot 0.11.2", + "once_cell", + "parking_lot", "phf_shared 0.10.0", "precomputed-hash", "serde", @@ -4481,12 +4571,12 @@ dependencies = [ [[package]] name = "string_cache_codegen" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "proc-macro2", "quote", ] @@ -4536,9 +4626,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -4572,22 +4662,22 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.0.2" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a45a1c4c9015217e12347f2a411b57ce2c4fc543913b14b6fe40483328e709" +checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" dependencies = [ - "cfg-expr 0.10.2", - "heck 0.4.0", + "cfg-expr 0.11.0", + "heck 0.4.1", "pkg-config", "toml", - "version-compare 0.1.0", + "version-compare 0.1.1", ] [[package]] name = "tao" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fa15735311b4816d030ff54da58560b047daca0970e1031aed5502e84231a8" +checksum = "ac8e6399427c8494f9849b58694754d7cc741293348a6836b6c8d2c5aa82d8e6" dependencies = [ "bitflags", "cairo-rs", @@ -4616,14 +4706,14 @@ dependencies = [ "ndk-sys", "objc", "once_cell", - "parking_lot 0.12.1", + "parking_lot", "paste", "png", "raw-window-handle", "scopeguard", "serde", "unicode-segmentation", - "uuid 1.2.2", + "uuid 1.3.0", "windows 0.39.0", "windows-implement", "x11-dl", @@ -4648,13 +4738,13 @@ dependencies = [ [[package]] name = "tauri" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8ea1d785ab2164373703817bff144c4610a69ad3f659becaca0e1ea004b98d8" +checksum = "5b48820ee3bb6a5031a83b2b6e11f8630bdc5a2f68cb841ab8ebc7a15a916679" dependencies = [ "anyhow", "attohttpc", - "base64", + "base64 0.13.1", "cocoa", "dirs-next", "embed_plist", @@ -4664,7 +4754,7 @@ dependencies = [ "glib", "glob", "gtk", - "heck 0.4.0", + "heck 0.4.1", "http", "ignore", "minisign-verify", @@ -4676,7 +4766,7 @@ dependencies = [ "raw-window-handle", "regex", "rfd", - "semver 1.0.6", + "semver 1.0.16", "serde", "serde_json", "serde_repr", @@ -4689,10 +4779,10 @@ dependencies = [ "tauri-utils", "tempfile", "thiserror", - "time 0.3.7", + "time", "tokio", "url", - "uuid 1.2.2", + "uuid 1.3.0", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -4707,9 +4797,9 @@ checksum = "8807c85d656b2b93927c19fe5a5f1f1f348f96c2de8b90763b3c2d561511f9b4" dependencies = [ "anyhow", "cargo_toml", - "heck 0.4.0", + "heck 0.4.1", "json-patch", - "semver 1.0.6", + "semver 1.0.16", "serde_json", "tauri-utils", "winres", @@ -4721,7 +4811,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14388d484b6b1b5dc0f6a7d6cc6433b3b230bec85eaa576adcdf3f9fafa49251" dependencies = [ - "base64", + "base64 0.13.1", "brotli", "ico", "json-patch", @@ -4730,14 +4820,14 @@ dependencies = [ "proc-macro2", "quote", "regex", - "semver 1.0.6", + "semver 1.0.16", "serde", "serde_json", - "sha2 0.10.2", + "sha2 0.10.6", "tauri-utils", "thiserror", - "time 0.3.7", - "uuid 1.2.2", + "time", + "uuid 1.3.0", "walkdir", ] @@ -4747,7 +4837,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "069319e5ecbe653a799b94b0690d9f9bf5d00f7b1d3989aa331c524d4e354075" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro2", "quote", "syn", @@ -4770,7 +4860,7 @@ dependencies = [ "serde_json", "tauri-utils", "thiserror", - "uuid 1.2.2", + "uuid 1.3.0", "webview2-com", "windows 0.39.0", ] @@ -4788,7 +4878,7 @@ dependencies = [ "raw-window-handle", "tauri-runtime", "tauri-utils", - "uuid 1.2.2", + "uuid 1.3.0", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -4804,7 +4894,7 @@ dependencies = [ "brotli", "ctor", "glob", - "heck 0.4.0", + "heck 0.4.1", "html5ever", "infer", "json-patch", @@ -4813,7 +4903,7 @@ dependencies = [ "phf 0.10.1", "proc-macro2", "quote", - "semver 1.0.6", + "semver 1.0.16", "serde", "serde_json", "serde_with", @@ -4864,7 +4954,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.7", + "time", "zeroize", ] @@ -4897,7 +4987,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.7", + "time", ] [[package]] @@ -4910,7 +5000,7 @@ dependencies = [ "bytes", "flex-error", "futures", - "getrandom 0.2.5", + "getrandom 0.2.8", "http", "hyper", "hyper-proxy", @@ -4925,7 +5015,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.7", + "time", "tokio", "tracing", "url", @@ -4935,9 +5025,9 @@ dependencies = [ [[package]] name = "tendril" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", "mac", @@ -4946,19 +5036,13 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" - [[package]] name = "thin-slice" version = "0.1.1" @@ -4967,18 +5051,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -4987,46 +5071,48 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] [[package]] name = "time" -version = "0.1.43" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" -dependencies = [ - "itoa 1.0.1", + "itoa 1.0.5", "libc", "num_threads", + "serde", + "time-core", "time-macros", ] [[package]] -name = "time-macros" -version = "0.2.3" +name = "time-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] [[package]] name = "tokio" -version = "1.21.2" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg 1.1.0", "bytes", @@ -5034,19 +5120,19 @@ dependencies = [ "memchr", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -5055,9 +5141,9 @@ dependencies = [ [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -5076,38 +5162,55 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", "futures-sink", - "log", "pin-project-lite", "tokio", + "tracing", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] -name = "tower-service" -version = "0.3.1" +name = "toml_datetime" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" + +[[package]] +name = "toml_edit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" +dependencies = [ + "indexmap", + "nom8", + "toml_datetime", +] + +[[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.31" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", @@ -5117,9 +5220,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -5128,19 +5231,19 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ - "lazy_static", + "once_cell", "valuable", ] [[package]] name = "tracing-log" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", "log", @@ -5149,16 +5252,16 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.9" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ - "ansi_term", - "lazy_static", "matchers", + "nu-ansi-term", + "once_cell", "regex", "sharded-slab", - "smallvec 1.8.0", + "smallvec 1.10.0", "thread_local", "tracing", "tracing-core", @@ -5176,15 +5279,15 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ts-rs" -version = "6.1.2" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d26cba30c9b3a2f537f765cf754126f11c983b7426d280ae0b4cef2374cab98" +checksum = "4added4070a4fdf9df03457206cd2e4b12417c8560a2954d91ffcbe60177a56a" dependencies = [ "thiserror", "ts-rs-macros", @@ -5192,9 +5295,9 @@ dependencies = [ [[package]] name = "ts-rs-macros" -version = "6.1.2" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd8e302fbcf5b60dfa1df443535967511442c5ce4eac8b4c9fa811f2274280a4" +checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff" dependencies = [ "Inflector", "proc-macro2", @@ -5205,21 +5308,21 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy", @@ -5229,15 +5332,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -5250,15 +5353,15 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" @@ -5266,7 +5369,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.5", + "generic-array", "subtle", ] @@ -5302,25 +5405,11 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" dependencies = [ - "getrandom 0.2.5", -] - -[[package]] -name = "validator-api-requests" -version = "0.1.0" -dependencies = [ - "bs58", - "coconut-interface", - "cosmrs", - "cosmwasm-std", - "getset", - "mixnet-contract-common", - "schemars", - "serde", + "getrandom 0.2.8", ] [[package]] @@ -5328,25 +5417,30 @@ name = "validator-client" version = "0.1.0" dependencies = [ "async-trait", - "base64", + "base64 0.13.1", "bip39", - "coconut-bandwidth-contract-common", - "coconut-dkg-common", - "coconut-interface", "colored 2.0.0", - "config", - "contracts-common", "cosmrs", "cosmwasm-std", "cw3", - "execute", + "cw4", "flate2", "futures", "itertools", "log", - "mixnet-contract-common", - "multisig-contract-common", - "network-defaults", + "nym-api-requests", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", + "nym-coconut-interface", + "nym-config", + "nym-contracts-common", + "nym-execute", + "nym-group-contract-common", + "nym-mixnet-contract-common", + "nym-multisig-contract-common", + "nym-network-defaults", + "nym-vesting-contract", + "nym-vesting-contract-common", "prost", "reqwest", "serde", @@ -5355,9 +5449,7 @@ dependencies = [ "thiserror", "tokio", "url", - "validator-api-requests", - "vesting-contract", - "vesting-contract-common", + "zeroize", ] [[package]] @@ -5374,19 +5466,19 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "5.1.17" +version = "7.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e" +checksum = "447f9238a4553957277b3ee09d80babeae0811f1b3baefb093de1c0448437a37" dependencies = [ "anyhow", "cfg-if", - "chrono", "enum-iterator", "getset", "git2", "rustc_version 0.4.0", "rustversion", "thiserror", + "time", ] [[package]] @@ -5397,9 +5489,9 @@ checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" [[package]] name = "version-compare" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" [[package]] name = "version_check" @@ -5407,32 +5499,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vesting-contract" -version = "1.1.2" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "cw-storage-plus", - "mixnet-contract-common", - "schemars", - "serde", - "thiserror", - "vergen", - "vesting-contract-common", -] - -[[package]] -name = "vesting-contract-common" -version = "0.1.0" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "mixnet-contract-common", - "schemars", - "serde", -] - [[package]] name = "walkdir" version = "2.3.2" @@ -5460,12 +5526,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -5474,9 +5534,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -5484,13 +5544,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -5499,9 +5559,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if", "js-sys", @@ -5511,9 +5571,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5521,9 +5581,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -5534,15 +5594,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" -version = "0.3.56" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -5592,7 +5652,7 @@ dependencies = [ "pango-sys", "pkg-config", "soup2-sys", - "system-deps 6.0.2", + "system-deps 6.0.3", ] [[package]] @@ -5738,15 +5798,41 @@ checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 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", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", ] [[package]] @@ -5756,10 +5842,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" [[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" +name = "windows_aarch64_gnullvm" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" @@ -5774,10 +5860,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" [[package]] -name = "windows_i686_gnu" -version = "0.36.1" +name = "windows_aarch64_msvc" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" @@ -5792,10 +5878,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" [[package]] -name = "windows_i686_msvc" -version = "0.36.1" +name = "windows_i686_gnu" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" @@ -5810,10 +5896,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" [[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" +name = "windows_i686_msvc" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" @@ -5828,10 +5914,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" [[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" +name = "windows_x86_64_gnu" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" @@ -5846,10 +5938,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" [[package]] -name = "winreg" -version = "0.7.0" +name = "windows_x86_64_msvc" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] @@ -5869,7 +5967,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c1ad8e2424f554cc5bdebe8aa374ef5b433feff817aebabca0389961fc7ef98" dependencies = [ - "base64", + "base64 0.13.1", "block", "cocoa", "core-graphics", @@ -5889,7 +5987,7 @@ dependencies = [ "once_cell", "serde", "serde_json", - "sha2 0.10.2", + "sha2 0.10.6", "soup2", "tao", "thiserror", @@ -5912,9 +6010,9 @@ dependencies = [ [[package]] name = "x11" -version = "2.19.1" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd0565fa8bfba8c5efe02725b14dff114c866724eff2cfd44d76cea74bcd87a" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" dependencies = [ "libc", "pkg-config", @@ -5922,44 +6020,38 @@ dependencies = [ [[package]] name = "x11-dl" -version = "2.20.1" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1536d6965a5d4e573c7ef73a2c15ebcd0b2de3347bdf526c34c297c00ac40f0" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ - "lazy_static", "libc", + "once_cell", "pkg-config", ] [[package]] name = "xattr" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" dependencies = [ "libc", ] -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - [[package]] name = "zeroize" -version = "1.4.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", @@ -5969,9 +6061,9 @@ dependencies = [ [[package]] name = "zip" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf225bcf73bb52cbb496e70475c7bd7a3f769df699c0020f6c7bd9a96dcf0b8d" +checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef" dependencies = [ "byteorder", "crc32fast", diff --git a/nym-wallet/nym-wallet-recovery-cli/Cargo.toml b/nym-wallet/nym-wallet-recovery-cli/Cargo.toml index 909467a6e0..30d478d3b3 100644 --- a/nym-wallet/nym-wallet-recovery-cli/Cargo.toml +++ b/nym-wallet/nym-wallet-recovery-cli/Cargo.toml @@ -11,9 +11,9 @@ anyhow = "1.0" argon2 = "0.4" base64 = "0.13" bip39 = "1.0" -clap = { version = "3.2", features = ["derive"] } +clap = { version = "4.0", features = ["derive"] } log = "0.4" pretty_env_logger = "0.4" serde_json = "1.0.0" -logging = { path = "../../common/logging" } \ No newline at end of file +nym-bin-common = { path = "../../common/bin-common" } diff --git a/nym-wallet/nym-wallet-recovery-cli/src/main.rs b/nym-wallet/nym-wallet-recovery-cli/src/main.rs index 7cb2c3e0d7..d12d2bbaf7 100644 --- a/nym-wallet/nym-wallet-recovery-cli/src/main.rs +++ b/nym-wallet/nym-wallet-recovery-cli/src/main.rs @@ -12,7 +12,7 @@ use aes_gcm::{aead::Aead, Aes256Gcm, Key, NewAead, Nonce}; use anyhow::{anyhow, Result}; use argon2::{Algorithm, Argon2, Params, Version}; use clap::Parser; -use logging::setup_logging; +use nym_bin_common::logging::setup_logging; use serde_json::Value; // Mostly defaults @@ -82,7 +82,7 @@ fn decrypt_file(file: File, passwords: &[String], parse: &ParseMode) -> Result<( match decrypt_login(login, passwords, parse) { Ok(is_success) if is_success => successes += 1, Ok(_) => println!("None of the provided passwords succeeded"), - Err(err) => println!("Failed: {}", err), + Err(err) => println!("Failed: {err}"), } } @@ -112,7 +112,7 @@ fn decrypt_login(login: &Value, passwords: &[String], parse: &ParseMode) -> Resu match result { Ok(DecryptedData::Raw(raw_decrypt)) => { println!(" success!"); - println!("{}", raw_decrypt); + println!("{raw_decrypt}"); return Ok(true); } Ok(DecryptedData::Json(json_decrypt)) => match parse_results(&json_decrypt) { @@ -133,9 +133,9 @@ fn decrypt_login(login: &Value, passwords: &[String], parse: &ParseMode) -> Resu } return Ok(true); } - Err(err) => println!(" failed to parse\n{}", err), + Err(err) => println!(" failed to parse\n{err}"), }, - Err(err) => println!(" failed\n{}", err), + Err(err) => println!(" failed\n{err}"), } } @@ -160,10 +160,10 @@ fn get_login_entry(login: &Value) -> Result<(&str, &str, &str)> { fn base64_decode(ciphertext: &str, iv: &str, salt: &str) -> Result<(Vec, Vec, Vec)> { let ciphertext = base64::decode(ciphertext) - .map_err(|err| anyhow!("Unable to base64 decode ciphertext: {}", err))?; - let iv = base64::decode(iv).map_err(|err| anyhow!("Unable to base64 decode iv: {}", err))?; + .map_err(|err| anyhow!("Unable to base64 decode ciphertext: {err}"))?; + let iv = base64::decode(iv).map_err(|err| anyhow!("Unable to base64 decode iv: {err}"))?; let salt = - base64::decode(salt).map_err(|err| anyhow!("Unable to base64 decode salt: {}", err))?; + base64::decode(salt).map_err(|err| anyhow!("Unable to base64 decode salt: {err}"))?; Ok((ciphertext, iv, salt)) } @@ -186,7 +186,7 @@ fn decrypt_password( let mut key = Key::default(); argon2 .hash_password_into(password.as_bytes(), salt, &mut key) - .map_err(|err| anyhow!("Unable to hash password: {}", err))?; + .map_err(|err| anyhow!("Unable to hash password: {err}"))?; // Create the Cipher let cipher = Aes256Gcm::new(&key); diff --git a/nym-wallet/nym-wallet-types/Cargo.toml b/nym-wallet/nym-wallet-types/Cargo.toml index 322aca5c06..f8ebba8263 100644 --- a/nym-wallet/nym-wallet-types/Cargo.toml +++ b/nym-wallet/nym-wallet-types/Cargo.toml @@ -14,21 +14,21 @@ ts-rs = "6.1.2" cosmwasm-std = "1.0.0-beta8" cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } -config = { path = "../../common/config" } -network-defaults = { path = "../../common/network-defaults" } -mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-config = { path = "../../common/config" } +nym-network-defaults = { path = "../../common/network-defaults" } +nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } validator-client = { path = "../../common/client-libs/validator-client", features = [ - "nymd-client", + "nyxd-client", ] } -vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } +nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } # Used for Type conversion, can be extracted but its a lot of work -vesting-contract = { path = "../../contracts/vesting" } +nym-vesting-contract = { path = "../../contracts/vesting" } nym-types = { path = "../../common/types" } [features] default = [] -generate-ts = ["mixnet-contract-common/generate-ts", "nym-types/generate-ts"] +generate-ts = ["nym-mixnet-contract-common/generate-ts", "nym-types/generate-ts"] diff --git a/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/AppEnv.ts b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/AppEnv.ts index 3f47419469..49ebfc27d1 100644 --- a/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/AppEnv.ts +++ b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/AppEnv.ts @@ -1,2 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export interface AppEnv { ADMIN_ADDRESS: string | null, SHOW_TERMINAL: string | null, ENABLE_QA_MODE: string | null, } \ No newline at end of file diff --git a/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrl.ts b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrl.ts index 9c74679bac..7e94537f97 100644 --- a/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrl.ts +++ b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrl.ts @@ -1,2 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export interface ValidatorUrl { url: string, name: string | null, } \ No newline at end of file diff --git a/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrls.ts b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrls.ts index 2ac6693e0b..36166951ee 100644 --- a/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrls.ts +++ b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrls.ts @@ -1,2 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export interface Validator { nymd_url: string, nymd_name: string | null, api_url: string | null, } \ No newline at end of file +export interface Validator { nyxd_url: string, nyxd_name: string | null, api_url: string | null, } \ No newline at end of file diff --git a/nym-wallet/nym-wallet-types/src/admin.rs b/nym-wallet/nym-wallet-types/src/admin.rs index 183ecb7a8b..23fc67cecb 100644 --- a/nym-wallet/nym-wallet-types/src/admin.rs +++ b/nym-wallet/nym-wallet-types/src/admin.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use mixnet_contract_common::ContractStateParams; +use nym_mixnet_contract_common::ContractStateParams; use nym_types::currency::{DecCoin, RegisteredCoins}; use nym_types::error::TypesError; use serde::{Deserialize, Serialize}; diff --git a/nym-wallet/nym-wallet-types/src/interval.rs b/nym-wallet/nym-wallet-types/src/interval.rs index c4937b4922..f9772c9082 100644 --- a/nym-wallet/nym-wallet-types/src/interval.rs +++ b/nym-wallet/nym-wallet-types/src/interval.rs @@ -1,7 +1,7 @@ // Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use mixnet_contract_common::{EpochId, Interval as ContractInterval, IntervalId}; +use nym_mixnet_contract_common::{EpochId, Interval as ContractInterval, IntervalId}; use serde::{Deserialize, Serialize}; // TODO: ask @MS why we can't just use ContractInterval directly diff --git a/nym-wallet/nym-wallet-types/src/network.rs b/nym-wallet/nym-wallet-types/src/network.rs index 3a39bd6733..8a443243e7 100644 --- a/nym-wallet/nym-wallet-types/src/network.rs +++ b/nym-wallet/nym-wallet-types/src/network.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use config::defaults::{mainnet, DenomDetails, NymNetworkDetails}; +use nym_config::defaults::{mainnet, DenomDetails, NymNetworkDetails}; use nym_types::{currency::DecCoin, error::TypesError}; use serde::{Deserialize, Serialize}; use std::{fmt, ops::Not, str::FromStr}; @@ -62,7 +62,7 @@ impl Default for Network { impl fmt::Display for Network { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) + write!(f, "{self:?}") } } @@ -94,7 +94,7 @@ fn parse_optional_str(raw: &str) -> Option { } mod sandbox { - use network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDetails}; + use nym_network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDetails}; use super::parse_optional_str; @@ -109,6 +109,7 @@ mod sandbox { "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv"; pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r"; + pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = @@ -128,8 +129,8 @@ mod sandbox { )] } - pub(crate) fn network_details() -> network_defaults::NymNetworkDetails { - network_defaults::NymNetworkDetails { + pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { + nym_network_defaults::NymNetworkDetails { chain_details: ChainDetails { bech32_account_prefix: BECH32_PREFIX.to_string(), mix_denom: MIX_DENOM.into(), @@ -145,6 +146,7 @@ mod sandbox { coconut_bandwidth_contract_address: parse_optional_str( COCONUT_BANDWIDTH_CONTRACT_ADDRESS, ), + group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS), multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS), coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS), }, @@ -153,7 +155,7 @@ mod sandbox { } mod qa { - use network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDetails}; + use nym_network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDetails}; use super::parse_optional_str; @@ -170,6 +172,7 @@ mod qa { "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw"; + pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs"; pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs"; pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs"; @@ -187,8 +190,8 @@ mod qa { )] } - pub(crate) fn network_details() -> network_defaults::NymNetworkDetails { - network_defaults::NymNetworkDetails { + pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { + nym_network_defaults::NymNetworkDetails { chain_details: ChainDetails { bech32_account_prefix: BECH32_PREFIX.to_string(), mix_denom: MIX_DENOM.into(), @@ -204,6 +207,7 @@ mod qa { coconut_bandwidth_contract_address: parse_optional_str( COCONUT_BANDWIDTH_CONTRACT_ADDRESS, ), + group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS), multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS), coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS), }, diff --git a/nym-wallet/nym-wallet-types/src/network_config.rs b/nym-wallet/nym-wallet-types/src/network_config.rs index b1f987d5b0..8d867d1768 100644 --- a/nym-wallet/nym-wallet-types/src/network_config.rs +++ b/nym-wallet/nym-wallet-types/src/network_config.rs @@ -32,19 +32,19 @@ pub struct ValidatorUrl { )] #[derive(Debug, Serialize, Deserialize)] pub struct Validator { - pub nymd_url: String, - pub nymd_name: Option, + pub nyxd_url: String, + pub nyxd_name: Option, pub api_url: Option, } impl fmt::Display for Validator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let nymd_url = format!("nymd_url: {}", self.nymd_url); + let nyxd_url = format!("nyxd_url: {}", self.nyxd_url); let api_url = self .api_url .as_ref() - .map(|api_url| format!(", api_url: {}", api_url)) + .map(|api_url| format!(", api_url: {api_url}")) .unwrap_or_default(); - write!(f, "{nymd_url}{api_url}") + write!(f, "{nyxd_url}{api_url}") } } diff --git a/nym-wallet/package.json b/nym-wallet/package.json index f2f827c098..05d6195526 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/nym-wallet-app", - "version": "1.1.4", + "version": "1.1.12", "main": "index.js", "license": "MIT", "scripts": { @@ -31,7 +31,7 @@ "@nymproject/mui-theme": "^1.0.0", "@nymproject/react": "^1.0.0", "@nymproject/types": "^1.0.0", - "@storybook/react": "^6.5.8", + "@storybook/react": "^6.5.15", "@tauri-apps/api": "^1.2.0", "@tauri-apps/tauri-forage": "^1.0.0-beta.2", "big.js": "^6.2.1", @@ -42,8 +42,8 @@ "notistack": "^2.0.3", "npm-run-all": "^4.1.5", "qrcode.react": "^1.0.1", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-error-boundary": "^3.1.3", "react-hook-form": "^7.14.2", "react-router-dom": "6", @@ -52,7 +52,8 @@ "string-to-color": "^2.2.2", "use-clipboard-copy": "^0.2.0", "uuid": "^8.3.2", - "yup": "^0.32.9" + "yup": "^0.32.9", + "zxcvbn": "^4.4.2" }, "devDependencies": { "@babel/core": "^7.15.0", @@ -62,7 +63,7 @@ "@babel/preset-typescript": "^7.15.0", "@nymproject/eslint-config-react-typescript": "^1.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", - "@storybook/react": "^6.5.8", + "@storybook/react": "^6.5.15", "@svgr/webpack": "^6.1.1", "@tauri-apps/cli": "^1.0.5", "@testing-library/jest-dom": "^5.14.1", @@ -72,16 +73,17 @@ "@types/jest": "^27.0.1", "@types/node": "^16.7.13", "@types/qrcode.react": "^1.0.2", - "@types/react": "^17.0.34", - "@types/react-dom": "^17.0.9", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.10", "@types/semver": "^7.3.8", "@types/uuid": "^8.3.4", + "@types/zxcvbn": "^4.4.1", "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.13.0", - "babel-loader": "^8.2.2", + "babel-loader": "^8.3.0", "babel-plugin-root-import": "^6.6.0", "clean-webpack-plugin": "^4.0.0", - "css-loader": "^6.2.0", + "css-loader": "^6.7.3", "css-minimizer-webpack-plugin": "^3.0.2", "dotenv-webpack": "^7.0.3", "eslint": "^8.10.0", @@ -107,17 +109,17 @@ "prettier": "2.3.2", "react-refresh": "^0.10.0", "react-refresh-typescript": "^2.0.2", - "style-loader": "^3.2.1", + "style-loader": "^3.3.1", "thread-loader": "^3.0.4", "ts-jest": "^27.0.5", - "ts-loader": "^9.2.5", + "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^3.5.2", "typescript": "^4.6.2", "url-loader": "^4.1.1", - "webpack": "^5.64.3", + "webpack": "^5.75.0", "webpack-cli": "^4.8.0", "webpack-dev-server": "^4.5.0", "webpack-favicons": "^1.3.8", "webpack-merge": "^5.8.0" } -} +} \ No newline at end of file diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index edbe2592b7..81b001f0bf 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.1.4" +version = "1.1.12" description = "Nym Native Wallet" authors = ["Nym Technologies SA"] license = "" @@ -21,10 +21,9 @@ tauri-macros = "=1.2.1" [dependencies] bip39 = "1.0" cfg-if = "1.0.0" -chrono = "0.4" colored = "2.0" dirs = "4.0" -dotenv = "0.15.0" +dotenvy = "0.15.6" eyre = "0.6.5" fern = { version = "0.6.1", features = ["colored"] } futures = "0.3.15" @@ -38,8 +37,9 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_repr = "0.1" strum = { version = "0.23", features = ["derive"] } -tauri = { version = "=1.2.2", features = ["clipboard-all", "shell-open", "updater", "window-maximize"] } +tauri = { version = "=1.2.3", features = ["clipboard-all", "shell-open", "updater", "window-maximize"] } tendermint-rpc = "0.23.0" +time = { version = "0.3.17", features = ["local-offset"] } thiserror = "1.0" tokio = { version = "1.10", features = ["full"] } toml = "0.5.8" @@ -55,14 +55,14 @@ cosmwasm-std = "1.0.0" cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } validator-client = { path = "../../common/client-libs/validator-client", features = [ - "nymd-client", + "nyxd-client", ] } -mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } -vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } -config = { path = "../../common/config" } -coconut-interface = { path = "../../common/coconut-interface" } +nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } +nym-config = { path = "../../common/config" } +nym-coconut-interface = { path = "../../common/coconut-interface" } # Used for Type conversion, can be extracted but its a lot of work -vesting-contract = { path = "../../contracts/vesting" } +nym-vesting-contract = { path = "../../contracts/vesting" } nym-types = { path = "../../common/types" } nym-wallet-types = { path = "../nym-wallet-types" } diff --git a/nym-wallet/src-tauri/src/coin.rs b/nym-wallet/src-tauri/src/coin.rs index 79b19171d9..4195a1736d 100644 --- a/nym-wallet/src-tauri/src/coin.rs +++ b/nym-wallet/src-tauri/src/coin.rs @@ -6,9 +6,9 @@ use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::str::FromStr; use strum::IntoEnumIterator; -use validator_client::nymd::CosmosCoin; -use validator_client::nymd::Denom as CosmosDenom; -use validator_client::nymd::{Coin as BackendCoin, CosmWasmCoin}; +use validator_client::nyxd::CosmosCoin; +use validator_client::nyxd::Denom as CosmosDenom; +use validator_client::nyxd::{Coin as BackendCoin, CosmWasmCoin}; const MINOR_IN_MAJOR: f64 = 1_000_000.; diff --git a/nym-wallet/src-tauri/src/config/mod.rs b/nym-wallet/src-tauri/src/config/mod.rs index 9da3a1b73e..b1c621543a 100644 --- a/nym-wallet/src-tauri/src/config/mod.rs +++ b/nym-wallet/src-tauri/src/config/mod.rs @@ -11,16 +11,16 @@ use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use url::Url; -use validator_client::nymd::AccountId as CosmosAccountId; +use validator_client::nyxd::AccountId as CosmosAccountId; -use config::defaults::{DenomDetailsOwned, NymNetworkDetails, ValidatorDetails}; +use nym_config::defaults::{DenomDetailsOwned, NymNetworkDetails, ValidatorDetails}; use nym_wallet_types::network::Network as WalletNetwork; use nym_wallet_types::network_config; use crate::error::BackendError; use crate::platform_constants::{CONFIG_DIR_NAME, CONFIG_FILENAME}; -pub const REMOTE_SOURCE_OF_VALIDATOR_URLS: &str = +pub const REMOTE_SOURCE_OF_NYXD_URLS: &str = "https://nymtech.net/.wellknown/wallet/validators.json"; const CURRENT_GLOBAL_CONFIG_VERSION: u32 = 1; @@ -56,12 +56,12 @@ pub struct NetworkConfig { version: Option, // User selected urls - selected_nymd_url: Option, + selected_nyxd_url: Option, selected_api_url: Option, // Additional user provided validators. - // It is an option for the purpuse of file serialization. - validator_urls: Option>, + // It is an option for the purpose of file serialization. + nyxd_urls: Option>, } impl Default for Base { @@ -85,16 +85,16 @@ impl Default for NetworkConfig { fn default() -> Self { Self { version: Some(CURRENT_NETWORK_CONFIG_VERSION), - selected_nymd_url: None, + selected_nyxd_url: None, selected_api_url: None, - validator_urls: None, + nyxd_urls: None, } } } impl NetworkConfig { fn validators(&self) -> impl Iterator { - self.validator_urls.iter().flat_map(|v| v.iter()) + self.nyxd_urls.iter().flat_map(|v| v.iter()) } } @@ -167,7 +167,7 @@ impl Config { Some(global) } Err(err) => { - log::trace!("Not loading {:#?}: {}", file, err); + log::trace!("Not loading {:#?}: {err}", file); None } } @@ -182,7 +182,7 @@ impl Config { log::trace!("Loaded from file {:#?}", file); networks.insert(network.as_key(), config); } - Err(err) => log::trace!("Not loading {:#?}: {}", file, err), + Err(err) => log::trace!("Not loading {:#?}: {err}", file), }; } @@ -241,41 +241,41 @@ impl Config { .expect("Wrong format for bandwidth claim contract address") } - pub fn select_validator_nymd_url(&mut self, nymd_url: Url, network: WalletNetwork) { + pub fn select_nyxd_url(&mut self, nyxd_url: Url, network: WalletNetwork) { if let Some(net) = self.networks.get_mut(&network.as_key()) { - net.selected_nymd_url = Some(nymd_url); + net.selected_nyxd_url = Some(nyxd_url); } else { self.networks.insert( network.as_key(), NetworkConfig { - selected_nymd_url: Some(nymd_url), + selected_nyxd_url: Some(nyxd_url), ..NetworkConfig::default() }, ); } } - pub fn select_validator_api_url(&mut self, api_url: Url, network: WalletNetwork) { + pub fn select_nym_api_url(&mut self, api_url: Url, network: WalletNetwork) { if let Some(net) = self.networks.get_mut(&network.as_key()) { net.selected_api_url = Some(api_url); } else { self.networks.insert( network.as_key(), NetworkConfig { - selected_nymd_url: Some(api_url), + selected_nyxd_url: Some(api_url), ..NetworkConfig::default() }, ); } } - pub fn get_selected_validator_nymd_url(&self, network: WalletNetwork) -> Option { + pub fn get_selected_validator_nyxd_url(&self, network: WalletNetwork) -> Option { self.networks .get(&network.as_key()) - .and_then(|config| config.selected_nymd_url.clone()) + .and_then(|config| config.selected_nyxd_url.clone()) } - pub fn get_selected_validator_api_url(&self, network: &WalletNetwork) -> Option { + pub fn get_selected_nym_api_url(&self, network: &WalletNetwork) -> Option { self.networks .get(&network.as_key()) .and_then(|config| config.selected_api_url.clone()) @@ -283,16 +283,16 @@ impl Config { pub fn add_validator_url(&mut self, url: ValidatorConfigEntry, network: WalletNetwork) { if let Some(network_config) = self.networks.get_mut(&network.as_key()) { - if let Some(ref mut urls) = network_config.validator_urls { + if let Some(ref mut urls) = network_config.nyxd_urls { urls.push(url); } else { - network_config.validator_urls = Some(vec![url]); + network_config.nyxd_urls = Some(vec![url]); } } else { self.networks.insert( network.as_key(), NetworkConfig { - validator_urls: Some(vec![url]), + nyxd_urls: Some(vec![url]), ..NetworkConfig::default() }, ); @@ -301,7 +301,7 @@ impl Config { pub fn remove_validator_url(&mut self, url: ValidatorConfigEntry, network: WalletNetwork) { if let Some(network_config) = self.networks.get_mut(&network.as_key()) { - if let Some(ref mut urls) = network_config.validator_urls { + if let Some(ref mut urls) = network_config.nyxd_urls { // Removes duplicates too if there are any urls.retain(|existing_url| existing_url != &url); } @@ -321,8 +321,8 @@ where #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct ValidatorConfigEntry { - pub nymd_url: Url, - pub nymd_name: Option, + pub nyxd_url: Url, + pub nyxd_name: Option, pub api_url: Option, } @@ -331,8 +331,8 @@ impl TryFrom for ValidatorConfigEntry { fn try_from(validator: ValidatorDetails) -> Result { Ok(ValidatorConfigEntry { - nymd_url: validator.nymd_url.parse()?, - nymd_name: None, + nyxd_url: validator.nyxd_url.parse()?, + nyxd_name: None, api_url: match &validator.api_url { Some(url) => Some(url.parse()?), None => None, @@ -346,8 +346,8 @@ impl TryFrom for ValidatorConfigEntry { fn try_from(validator: network_config::Validator) -> Result { Ok(ValidatorConfigEntry { - nymd_url: validator.nymd_url.parse()?, - nymd_name: validator.nymd_name, + nyxd_url: validator.nyxd_url.parse()?, + nyxd_name: validator.nyxd_name, api_url: match &validator.api_url { Some(url) => Some(url.parse()?), None => None, @@ -358,12 +358,9 @@ impl TryFrom for ValidatorConfigEntry { impl fmt::Display for ValidatorConfigEntry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s1 = format!("nymd_url: {}", self.nymd_url); - let name = self.nymd_name.as_ref().map(|name| format!(" ({})", name)); - let s2 = self - .api_url - .as_ref() - .map(|url| format!(", api_url: {}", url)); + let s1 = format!("nyxd_url: {}", self.nyxd_url); + let name = self.nyxd_name.as_ref().map(|name| format!(" ({name})")); + let s2 = self.api_url.as_ref().map(|url| format!(", api_url: {url}")); write!( f, " {}{}{},", @@ -416,7 +413,7 @@ impl fmt::Display for OptionalValidators { .as_ref() .map(|validators| format!(",\nqa: [\n{}\n]", validators.iter().format("\n"))) .unwrap_or_default(); - write!(f, "{}{}{}", s1, s2, s3) + write!(f, "{s1}{s2}{s3}") } } @@ -510,23 +507,23 @@ mod tests { fn test_config() -> Config { let netconfig = NetworkConfig { - selected_nymd_url: None, + selected_nyxd_url: None, selected_api_url: Some("https://my_api_url.com".parse().unwrap()), - validator_urls: Some(vec![ + nyxd_urls: Some(vec![ ValidatorConfigEntry { - nymd_url: "https://foo".parse().unwrap(), - nymd_name: Some("FooName".to_string()), + nyxd_url: "https://foo".parse().unwrap(), + nyxd_name: Some("FooName".to_string()), api_url: None, }, ValidatorConfigEntry { - nymd_url: "https://bar".parse().unwrap(), - nymd_name: None, + nyxd_url: "https://bar".parse().unwrap(), + nyxd_name: None, api_url: Some("https://bar/api".parse().unwrap()), }, ValidatorConfigEntry { - nymd_url: "https://baz".parse().unwrap(), - nymd_name: None, + nyxd_url: "https://baz".parse().unwrap(), + nyxd_name: None, api_url: Some("https://baz/api".parse().unwrap()), }, ]), @@ -551,16 +548,16 @@ mod tests { r#"version = 1 selected_api_url = 'https://my_api_url.com/' -[[validator_urls]] -nymd_url = 'https://foo/' -nymd_name = 'FooName' +[[nyxd_urls]] +nyxd_url = 'https://foo/' +nyxd_name = 'FooName' -[[validator_urls]] -nymd_url = 'https://bar/' +[[nyxd_urls]] +nyxd_url = 'https://bar/' api_url = 'https://bar/api' -[[validator_urls]] -nymd_url = 'https://baz/' +[[nyxd_urls]] +nyxd_url = 'https://baz/' api_url = 'https://baz/api' "# ); @@ -575,22 +572,22 @@ api_url = 'https://baz/api' serde_json::to_string_pretty(netconfig).unwrap(), r#"{ "version": 1, - "selected_nymd_url": null, + "selected_nyxd_url": null, "selected_api_url": "https://my_api_url.com/", - "validator_urls": [ + "nyxd_urls": [ { - "nymd_url": "https://foo/", - "nymd_name": "FooName", + "nyxd_url": "https://foo/", + "nyxd_name": "FooName", "api_url": null }, { - "nymd_url": "https://bar/", - "nymd_name": null, + "nyxd_url": "https://bar/", + "nyxd_name": null, "api_url": "https://bar/api" }, { - "nymd_url": "https://baz/", - "nymd_name": null, + "nyxd_url": "https://baz/", + "nyxd_name": null, "api_url": "https://baz/api" } ] @@ -611,12 +608,12 @@ api_url = 'https://baz/api' fn get_urls_parsed_from_config() { let config = test_config(); - let nymd_url = config + let nyxd_url = config .get_configured_validators(WalletNetwork::MAINNET) .next() - .map(|v| v.nymd_url) + .map(|v| v.nyxd_url) .unwrap(); - assert_eq!(nymd_url.as_ref(), "https://foo/"); + assert_eq!(nyxd_url.as_ref(), "https://foo/"); // The first entry is missing an API URL let api_url = config @@ -630,12 +627,12 @@ api_url = 'https://baz/api' fn get_urls_from_defaults() { let config = Config::default(); - let nymd_url = config + let nyxd_url = config .get_base_validators(WalletNetwork::MAINNET) .next() - .map(|v| v.nymd_url) + .map(|v| v.nyxd_url) .unwrap(); - assert_eq!(nymd_url.as_ref(), "https://rpc.nymtech.net/"); + assert_eq!(nyxd_url.as_ref(), "https://rpc.nymtech.net/"); let api_url = config .get_base_validators(WalletNetwork::MAINNET) diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index efef933c73..0166435c3e 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -4,8 +4,8 @@ use serde::{Serialize, Serializer}; use std::io; use std::num::ParseIntError; use thiserror::Error; -use validator_client::validator_api::error::ValidatorAPIError; -use validator_client::{nymd::error::NymdError, ValidatorClientError}; +use validator_client::nym_api::error::NymAPIError; +use validator_client::{nyxd::error::NyxdError, ValidatorClientError}; #[derive(Error, Debug)] pub enum BackendError { @@ -25,10 +25,10 @@ pub enum BackendError { source: tendermint_rpc::Error, }, #[error("{pretty_error}")] - NymdError { + NyxdError { pretty_error: String, #[source] - source: NymdError, + source: NyxdError, }, #[error("{source}")] CosmwasmStd { @@ -41,9 +41,9 @@ pub enum BackendError { source: eyre::Report, }, #[error("{source}")] - ValidatorApiError { + NymApiError { #[from] - source: ValidatorAPIError, + source: NymAPIError, }, #[error("{source}")] KeyDerivationError { @@ -111,7 +111,7 @@ pub enum BackendError { WalletUnexpectedMnemonicAccount, #[error("Failed to derive address from mnemonic")] FailedToDeriveAddress, - #[error("{0}")] + #[error(transparent)] ValueParseError(#[from] ParseIntError), #[error("The provided coin has an unknown denomination - {0}")] UnknownCoinDenom(String), @@ -121,6 +121,9 @@ pub enum BackendError { SignatureError(String), #[error("Unable to open a new window")] NewWindowError, + + #[error("This command ({name}) has been removed. Please try to use {alternative} instead.")] + RemovedCommand { name: String, alternative: String }, } impl Serialize for BackendError { @@ -132,29 +135,29 @@ impl Serialize for BackendError { } } -impl From for BackendError { - fn from(source: NymdError) -> Self { +impl From for BackendError { + fn from(source: NyxdError) -> Self { match source { - NymdError::AbciError { + NyxdError::AbciError { code: _, log: _, ref pretty_log, } => { if let Some(pretty_log) = pretty_log { - Self::NymdError { + Self::NyxdError { pretty_error: pretty_log.to_string(), source, } } else { - Self::NymdError { + Self::NyxdError { pretty_error: source.to_string(), source, } } } - nymd_error => Self::NymdError { - pretty_error: nymd_error.to_string(), - source: nymd_error, + nyxd_error => Self::NyxdError { + pretty_error: nyxd_error.to_string(), + source: nyxd_error, }, } } @@ -163,12 +166,10 @@ impl From for BackendError { impl From for BackendError { fn from(e: ValidatorClientError) -> Self { match e { - ValidatorClientError::ValidatorAPIError { source } => source.into(), + ValidatorClientError::NymAPIError { source } => source.into(), ValidatorClientError::MalformedUrlProvided(e) => e.into(), - ValidatorClientError::NymdError(e) => e.into(), - ValidatorClientError::NoAPIUrlAvailable => { - TypesError::NoValidatorApiUrlConfigured.into() - } + ValidatorClientError::NyxdError(e) => e.into(), + ValidatorClientError::NoAPIUrlAvailable => TypesError::NoNymApiUrlConfigured.into(), } } } diff --git a/nym-wallet/src-tauri/src/log.rs b/nym-wallet/src-tauri/src/log.rs index b673d023ae..8ffc883485 100644 --- a/nym-wallet/src-tauri/src/log.rs +++ b/nym-wallet/src-tauri/src/log.rs @@ -1,12 +1,35 @@ use std::str::FromStr; -use fern::colors::ColoredLevelConfig; +use fern::colors::{Color, ColoredLevelConfig}; use serde::Serialize; use serde_repr::{Deserialize_repr, Serialize_repr}; use tauri::Manager; +use time::{format_description, OffsetDateTime}; + +fn formatted_time() -> String { + // if we fail to obtain local time according to local offset, fallback to utc + let _now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()); + + // if we're running it in the unit test, use the hardcoded value + #[cfg(test)] + let _now = OffsetDateTime::from_unix_timestamp(1666666666).unwrap(); + + // the unwraps are fine as we know this description is correct + // note: the reason for this very particular format is a very simple one + // it's what we've always been using since we copied it from the example, + // so feel free to update it to whatever + let format = + format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]").unwrap(); + _now.format(&format).unwrap() +} pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerError> { - let colors = ColoredLevelConfig::new(); + let colors = ColoredLevelConfig::new() + .trace(Color::Magenta) + .debug(Color::Blue) + .info(Color::Green) + .warn(Color::Yellow) + .error(Color::Red); let base_config = fern::Dispatch::new() .level(global_level()) .filter_lowlevel_external_components() @@ -16,7 +39,7 @@ pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerE .format(move |out, message, record| { out.finish(format_args!( "{}[{}][{}] {}", - chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + formatted_time(), record.target(), colors.color(record.level()), message, @@ -28,7 +51,7 @@ pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerE .format(move |out, message, record| { out.finish(format_args!( "{}[{}] {}", - chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + formatted_time(), record.target(), message, )) @@ -111,3 +134,15 @@ impl From for LogLevel { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn log_formatting() { + let expected_chrono_formated = "[2022-10-25][02:57:46]".to_string(); + let new_time_based = formatted_time(); + assert_eq!(new_time_based, expected_chrono_formated) + } +} diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index d34a04f920..4677e46e69 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -5,14 +5,14 @@ use tauri::{Manager, Menu}; -use mixnet_contract_common::{Gateway, MixNode}; +use nym_mixnet_contract_common::{Gateway, MixNode}; use crate::menu::AddDefaultSubmenus; use crate::operations::help; use crate::operations::mixnet; +use crate::operations::nym_api; use crate::operations::signatures; use crate::operations::simulate; -use crate::operations::validator_api; use crate::operations::vesting; use crate::state::WalletState; @@ -29,7 +29,7 @@ mod wallet_storage; #[allow(clippy::too_many_lines)] fn main() { - dotenv::dotenv().ok(); + dotenvy::dotenv().ok(); let context = tauri::generate_context!(); tauri::Builder::default() @@ -83,14 +83,16 @@ fn main() { mixnet::rewards::claim_delegator_reward, mixnet::rewards::claim_operator_reward, mixnet::rewards::claim_locked_and_unlocked_delegator_reward, + mixnet::rewards::get_current_rewarding_parameters, mixnet::send::send, + mixnet::bond::get_mixnode_uptime, network_config::add_validator, - network_config::get_validator_api_urls, - network_config::get_validator_nymd_urls, + network_config::get_nym_api_urls, + network_config::get_nyxd_urls, network_config::remove_validator, - network_config::select_validator_api_url, - network_config::select_validator_nymd_url, - network_config::update_validator_urls, + network_config::select_nym_api_url, + network_config::select_nyxd_url, + network_config::update_nyxd_urls, state::load_config_from_files, state::save_config_to_files, utils::owns_gateway, @@ -99,13 +101,14 @@ fn main() { utils::get_old_and_incorrect_hardcoded_fee, utils::try_convert_pubkey_to_mix_id, utils::default_mixnode_cost_params, - validator_api::status::gateway_core_node_status, - validator_api::status::mixnode_core_node_status, - validator_api::status::mixnode_inclusion_probability, - validator_api::status::mixnode_reward_estimation, - validator_api::status::mixnode_stake_saturation, - validator_api::status::mixnode_status, - validator_api::status::gateway_report, + nym_api::status::compute_mixnode_reward_estimation, + nym_api::status::gateway_core_node_status, + nym_api::status::mixnode_core_node_status, + nym_api::status::mixnode_inclusion_probability, + nym_api::status::mixnode_reward_estimation, + nym_api::status::mixnode_stake_saturation, + nym_api::status::mixnode_status, + nym_api::status::gateway_report, vesting::rewards::vesting_claim_delegator_reward, vesting::rewards::vesting_claim_operator_reward, vesting::bond::vesting_bond_gateway, @@ -118,13 +121,22 @@ fn main() { vesting::bond::withdraw_vested_coins, vesting::delegate::vesting_delegate_to_mixnode, vesting::delegate::vesting_undelegate_from_mixnode, - vesting::queries::delegated_free, - vesting::queries::delegated_vesting, vesting::queries::get_account_info, vesting::queries::get_current_vesting_period, vesting::queries::locked_coins, vesting::queries::original_vesting, vesting::queries::spendable_coins, + vesting::queries::spendable_vested_coins, + vesting::queries::spendable_reward_coins, + vesting::queries::get_historical_vesting_staking_reward, + vesting::queries::get_spendable_vested_coins, + vesting::queries::get_spendable_reward_coins, + vesting::queries::get_delegated_coins, + vesting::queries::get_pledged_coins, + vesting::queries::get_withdrawn_coins, + vesting::queries::get_staked_coins, + vesting::queries::delegated_free, + vesting::queries::delegated_vesting, vesting::queries::vested_coins, vesting::queries::vesting_coins, vesting::queries::vesting_end_time, @@ -133,6 +145,7 @@ fn main() { vesting::queries::vesting_start_time, simulate::admin::simulate_update_contract_settings, simulate::cosmos::simulate_send, + simulate::cosmos::get_custom_fees, simulate::mixnet::simulate_bond_gateway, simulate::mixnet::simulate_unbond_gateway, simulate::mixnet::simulate_bond_mixnode, diff --git a/nym-wallet/src-tauri/src/network_config.rs b/nym-wallet/src-tauri/src/network_config.rs index 43e872b717..bcac19bb62 100644 --- a/nym-wallet/src-tauri/src/network_config.rs +++ b/nym-wallet/src-tauri/src/network_config.rs @@ -7,17 +7,17 @@ use nym_wallet_types::network::Network as WalletNetwork; use nym_wallet_types::network_config::{Validator, ValidatorUrl, ValidatorUrls}; #[tauri::command] -pub async fn get_validator_nymd_urls( +pub async fn get_nyxd_urls( network: WalletNetwork, state: tauri::State<'_, WalletState>, ) -> Result { let state = state.read().await; - let urls: Vec = state.get_nymd_urls(network).collect(); + let urls: Vec = state.get_nyxd_urls(network).collect(); Ok(ValidatorUrls { urls }) } #[tauri::command] -pub async fn get_validator_api_urls( +pub async fn get_nym_api_urls( network: WalletNetwork, state: tauri::State<'_, WalletState>, ) -> Result { @@ -27,27 +27,24 @@ pub async fn get_validator_api_urls( } #[tauri::command] -pub async fn select_validator_nymd_url( +pub async fn select_nyxd_url( url: &str, network: WalletNetwork, state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { - log::debug!("Selecting new validator nymd_url for {network}: {url}"); - state - .write() - .await - .select_validator_nymd_url(url, network)?; + log::debug!("Selecting new nyxd url for {network}: {url}"); + state.write().await.select_nyxd_url(url, network)?; Ok(()) } #[tauri::command] -pub async fn select_validator_api_url( +pub async fn select_nym_api_url( url: &str, network: WalletNetwork, state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { - log::debug!("Selecting new validator api_url for {network}: {url}"); - state.write().await.select_validator_api_url(url, network)?; + log::debug!("Selecting new nym api url for {network}: {url}"); + state.write().await.select_nym_api_url(url, network)?; Ok(()) } @@ -77,10 +74,8 @@ pub async fn remove_validator( // Update the list of validators by fecthing additional ones remotely. If it fails, just ignore. #[tauri::command] -pub async fn update_validator_urls( - state: tauri::State<'_, WalletState>, -) -> Result<(), BackendError> { +pub async fn update_nyxd_urls(state: tauri::State<'_, WalletState>) -> Result<(), BackendError> { let mut w_state = state.write().await; - let _r = w_state.fetch_updated_validator_urls().await; + let _r = w_state.fetch_updated_nyxd_urls().await; Ok(()) } diff --git a/nym-wallet/src-tauri/src/operations/help/log.rs b/nym-wallet/src-tauri/src/operations/help/log.rs index c8ff873f6a..c4c2357304 100644 --- a/nym-wallet/src-tauri/src/operations/help/log.rs +++ b/nym-wallet/src-tauri/src/operations/help/log.rs @@ -5,8 +5,8 @@ use tauri::Manager; pub fn help_log_toggle_window(app_handle: tauri::AppHandle) -> Result<(), BackendError> { if let Some(current_log_window) = app_handle.windows().get("log") { log::info!("Closing log window..."); - if let Err(e) = current_log_window.close() { - log::error!("Unable to close log window: {:?}", e); + if let Err(err) = current_log_window.close() { + log::error!("Unable to close log window: {err}"); } return Ok(()); } @@ -17,13 +17,13 @@ pub fn help_log_toggle_window(app_handle: tauri::AppHandle) -> Result<(), Backen .build() { Ok(window) => { - if let Err(e) = window.set_focus() { - log::error!("Unable to focus log window: {:?}", e); + if let Err(err) = window.set_focus() { + log::error!("Unable to focus log window: {err}"); } Ok(()) } - Err(e) => { - log::error!("Unable to create log window: {:?}", e); + Err(err) => { + log::error!("Unable to create log window: {err}"); Err(BackendError::NewWindowError) } } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 5811c007d1..fb145749ba 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -2,27 +2,27 @@ use crate::config::{Config, CUSTOM_SIMULATED_GAS_MULTIPLIER}; use crate::error::BackendError; use crate::network_config; use crate::state::{WalletAccountIds, WalletState}; -use crate::wallet_storage::{self, DEFAULT_LOGIN_ID}; +use crate::utils::{SensitiveStringWrapper, ZeroizeMnemonicWrapper}; +use crate::wallet_storage::{self, UserPassword, DEFAULT_LOGIN_ID}; use bip39::{Language, Mnemonic}; -use config::defaults::{NymNetworkDetails, COSMOS_DERIVATION_PATH}; use cosmrs::bip32::DerivationPath; use itertools::Itertools; +use nym_config::defaults::{NymNetworkDetails, COSMOS_DERIVATION_PATH}; use nym_types::account::{Account, AccountEntry, Balance}; use nym_wallet_types::network::Network as WalletNetwork; use rand::seq::SliceRandom; use std::collections::HashMap; -use std::str::FromStr; use strum::IntoEnumIterator; use url::Url; -use validator_client::nymd::wallet::{AccountData, DirectSecp256k1HdWallet}; -use validator_client::{nymd::SigningNymdClient, Client}; +use validator_client::nyxd::wallet::{AccountData, DirectSecp256k1HdWallet}; +use validator_client::{nyxd::SigningNyxdClient, Client}; #[tauri::command] pub async fn connect_with_mnemonic( - mnemonic: String, + mnemonic: SensitiveStringWrapper, state: tauri::State<'_, WalletState>, ) -> Result { - let mnemonic = Mnemonic::from_str(&mnemonic)?; + let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?; _connect_with_mnemonic(mnemonic, state).await } @@ -30,12 +30,12 @@ pub async fn connect_with_mnemonic( pub async fn get_balance(state: tauri::State<'_, WalletState>) -> Result { let guard = state.read().await; let client = guard.current_client()?; - let address = client.nymd.address(); + let address = client.nyxd.address(); let network = guard.current_network(); let base_mix_denom = network.base_mix_denom(); match client - .nymd + .nyxd .get_balance(address, base_mix_denom.to_string()) .await? { @@ -48,13 +48,13 @@ pub async fn get_balance(state: tauri::State<'_, WalletState>) -> Result String { - random_mnemonic().to_string() +pub fn create_new_mnemonic() -> SensitiveStringWrapper { + random_mnemonic().into_string() } #[tauri::command] -pub fn validate_mnemonic(mnemonic: &str) -> bool { - Mnemonic::from_str(mnemonic).is_ok() +pub fn validate_mnemonic(mnemonic: SensitiveStringWrapper) -> bool { + ZeroizeMnemonicWrapper::try_from_string(mnemonic).is_ok() } #[tauri::command] @@ -67,7 +67,7 @@ pub async fn switch_network( let client = r_state.client(network)?; let denom = network.mix_denom(); - Account::new(client.nymd.address().to_string(), denom) + Account::new(client.nyxd.address().to_string(), denom) }; let mut w_state = state.write().await; @@ -82,13 +82,15 @@ pub async fn logout(state: tauri::State<'_, WalletState>) -> Result<(), BackendE Ok(()) } -fn random_mnemonic() -> Mnemonic { +fn random_mnemonic() -> ZeroizeMnemonicWrapper { let mut rng = rand::thread_rng(); - Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap() + Mnemonic::generate_in_with(&mut rng, Language::English, 24) + .unwrap() + .into() } async fn _connect_with_mnemonic( - mnemonic: Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, state: tauri::State<'_, WalletState>, ) -> Result { { @@ -96,7 +98,7 @@ async fn _connect_with_mnemonic( w_state.load_config_files(); } - network_config::update_validator_urls(state.clone()).await?; + network_config::update_nyxd_urls(state.clone()).await?; let config = { let state = state.read().await; @@ -115,11 +117,11 @@ async fn _connect_with_mnemonic( }; // Get all the urls needed for the connection test - let (untested_nymd_urls, untested_api_urls) = { + let (untested_nyxd_urls, untested_api_urls) = { let state = state.read().await; - (state.get_all_nymd_urls(), state.get_all_api_urls()) + (state.get_all_nyxd_urls(), state.get_all_api_urls()) }; - let default_nymd_urls: HashMap = untested_nymd_urls + let default_nyxd_urls: HashMap = untested_nyxd_urls .iter() .map(|(network, urls)| (*network, urls.iter().next().unwrap().clone())) .collect(); @@ -128,18 +130,18 @@ async fn _connect_with_mnemonic( .map(|(network, urls)| (*network, urls.iter().next().unwrap().clone())) .collect(); - // Run connection tests on all nymd and validator-api endpoints - let (nymd_urls, api_urls) = - run_connection_test(untested_nymd_urls, untested_api_urls, &config).await; + // Run connection tests on all nyxd and nym-api endpoints + let (nyxd_urls, api_urls) = + run_connection_test(untested_nyxd_urls, untested_api_urls, &config).await; // Create clients for all networks let clients = create_clients( - &nymd_urls, + &nyxd_urls, &api_urls, - &default_nymd_urls, + &default_nyxd_urls, &default_api_urls, &config, - &mnemonic, + mnemonic.as_ref(), )?; // Set the default account @@ -149,7 +151,7 @@ async fn _connect_with_mnemonic( .find(|(network, _)| *network == default_network); let account_for_default_network = match client_for_default_network { Some((_, client)) => Ok(Account::new( - client.nymd.address().to_string(), + client.nyxd.address().to_string(), default_network.mix_denom(), )), None => Err(BackendError::NetworkNotSupported), @@ -170,7 +172,7 @@ async fn _connect_with_mnemonic( } async fn run_connection_test( - untested_nymd_urls: HashMap>, + untested_nyxd_urls: HashMap>, untested_api_urls: HashMap>, config: &Config, ) -> ( @@ -181,7 +183,7 @@ async fn run_connection_test( .map(|network| (network.into(), config.get_mixnet_contract_address(network))) .collect::>(); - let untested_nymd_urls = untested_nymd_urls + let untested_nyxd_urls = untested_nyxd_urls .into_iter() .flat_map(|(net, urls)| urls.into_iter().map(move |url| (net.into(), url))); @@ -190,7 +192,7 @@ async fn run_connection_test( .flat_map(|(net, urls)| urls.into_iter().map(move |url| (net.into(), url))); validator_client::connection_tester::run_validator_connection_test( - untested_nymd_urls, + untested_nyxd_urls, untested_api_urls, mixnet_contract_address, ) @@ -198,31 +200,31 @@ async fn run_connection_test( } fn create_clients( - nymd_urls: &HashMap>, + nyxd_urls: &HashMap>, api_urls: &HashMap>, - default_nymd_urls: &HashMap, + default_nyxd_urls: &HashMap, default_api_urls: &HashMap, config: &Config, mnemonic: &Mnemonic, -) -> Result)>, BackendError> { +) -> Result)>, BackendError> { let mut clients = Vec::new(); for network in WalletNetwork::iter() { - let nymd_url = if let Some(url) = config.get_selected_validator_nymd_url(network) { - log::debug!("Using selected nymd_url for {network}: {url}"); + let nyxd_url = if let Some(url) = config.get_selected_validator_nyxd_url(network) { + log::debug!("Using selected nyxd_url for {network}: {url}"); url.clone() } else { - let default_nymd_url = default_nymd_urls + let default_nyxd_url = default_nyxd_urls .get(&network) - .expect("Expected at least one nymd_url"); - select_random_responding_url(nymd_urls, network).unwrap_or_else(|| { + .expect("Expected at least one nyxd_url"); + select_random_responding_url(nyxd_urls, network).unwrap_or_else(|| { log::debug!( - "No successful nymd_urls for {network}: using default: {default_nymd_url}" + "No successful nyxd_urls for {network}: using default: {default_nyxd_url}" ); - default_nymd_url.clone() + default_nyxd_url.clone() }) }; - let api_url = if let Some(url) = config.get_selected_validator_api_url(&network) { + let api_url = if let Some(url) = config.get_selected_nym_api_url(&network) { log::debug!("Using selected api_url for {network}: {url}"); url.clone() } else { @@ -235,7 +237,7 @@ fn create_clients( }) }; - log::info!("Connecting to: nymd_url: {nymd_url} for {network}"); + log::info!("Connecting to: nyxd_url: {nyxd_url} for {network}"); log::info!("Connecting to: api_url: {api_url} for {network}"); let network_details = NymNetworkDetails::from(network) @@ -249,10 +251,10 @@ fn create_clients( )); let config = validator_client::Config::try_from_nym_network_details(&network_details)? - .with_urls(nymd_url, api_url); + .with_urls(nyxd_url, api_url); let mut client = validator_client::Client::new_signing(config, mnemonic.clone())?; - client.set_nymd_simulated_gas_multiplier(CUSTOM_SIMULATED_GAS_MULTIPLIER); + client.set_nyxd_simulated_gas_multiplier(CUSTOM_SIMULATED_GAS_MULTIPLIER); clients.push((network, client)); } Ok(clients) @@ -296,30 +298,31 @@ pub fn does_password_file_exist() -> Result { } #[tauri::command] -pub fn create_password(mnemonic: &str, password: String) -> Result<(), BackendError> { +pub fn create_password( + mnemonic: SensitiveStringWrapper, + password: UserPassword, +) -> Result<(), BackendError> { if does_password_file_exist()? { return Err(BackendError::WalletFileAlreadyExists); } log::info!("Creating password"); - let mnemonic = Mnemonic::from_str(mnemonic)?; + let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); // Currently we only support a single, default, login id in the wallet let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let password = wallet_storage::UserPassword::new(password); wallet_storage::store_login_with_multiple_accounts(mnemonic, hd_path, login_id, &password) } #[tauri::command] pub async fn sign_in_with_password( - password: String, + password: UserPassword, state: tauri::State<'_, WalletState>, ) -> Result { log::info!("Signing in with password"); // Currently we only support a single, default, id in the wallet let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let password = wallet_storage::UserPassword::new(password); let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; let mnemonic = extract_first_mnemonic(&stored_login)?; @@ -327,7 +330,7 @@ pub async fn sign_in_with_password( set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone()) .await?; - _connect_with_mnemonic(mnemonic, state).await + _connect_with_mnemonic(mnemonic.into(), state).await } fn extract_first_mnemonic( @@ -352,7 +355,7 @@ fn extract_first_mnemonic( #[tauri::command] pub async fn sign_in_with_password_and_account_id( account_id: &str, - password: &str, + password: UserPassword, state: tauri::State<'_, WalletState>, ) -> Result { log::info!("Signing in with password"); @@ -360,7 +363,6 @@ pub async fn sign_in_with_password_and_account_id( // Currently we only support a single, default, id in the wallet let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id.to_string()); - let password = wallet_storage::UserPassword::new(password.to_string()); let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; let mnemonic = extract_mnemonic(&stored_login, &account_id)?; @@ -368,7 +370,7 @@ pub async fn sign_in_with_password_and_account_id( set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone()) .await?; - _connect_with_mnemonic(mnemonic, state).await + _connect_with_mnemonic(mnemonic.into(), state).await } fn extract_mnemonic( @@ -402,18 +404,17 @@ pub fn archive_wallet_file() -> Result<(), BackendError> { #[tauri::command] pub async fn add_account_for_password( - mnemonic: &str, - password: &str, + mnemonic: SensitiveStringWrapper, + password: UserPassword, account_id: &str, state: tauri::State<'_, WalletState>, ) -> Result { log::info!("Adding account for the current password: {account_id}"); - let mnemonic = Mnemonic::from_str(mnemonic)?; + let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); // Currently we only support a single, default, login id in the wallet let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id.to_string()); - let password = wallet_storage::UserPassword::new(password.to_string()); wallet_storage::append_account_to_login( mnemonic.clone(), @@ -426,7 +427,13 @@ pub async fn add_account_for_password( let address = { let state = state.read().await; let network: NymNetworkDetails = state.current_network().into(); - derive_address(mnemonic, &network.chain_details.bech32_account_prefix)?.to_string() + // safety: the call to `clone_inner` is fine here as the raw mnemonic will get immediately + // passed to `DirectSecp256k1HdWallet` which will zeroize it on drop + derive_address( + mnemonic.into_cloned_inner(), + &network.chain_details.bech32_account_prefix, + )? + .to_string() }; // Re-read all the acccounts from the wallet to reset the state, rather than updating it @@ -450,12 +457,10 @@ async fn set_state_with_all_accounts( state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { log::trace!("Set state with accounts:"); - let all_accounts: Vec<_> = stored_login - .unwrap_into_multiple_accounts(first_id_when_converting) - .into_accounts() - .collect(); + let stored = stored_login.unwrap_into_multiple_accounts(first_id_when_converting); + let all_accounts = stored.inner(); - for account in &all_accounts { + for account in all_accounts { log::trace!("account: {:?}", account.id()); } @@ -490,7 +495,7 @@ async fn set_state_with_all_accounts( #[tauri::command] pub async fn remove_account_for_password( - password: &str, + password: UserPassword, account_id: &str, state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { @@ -498,7 +503,6 @@ pub async fn remove_account_for_password( // Currently we only support a single, default, id in the wallet let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id.to_string()); - let password = wallet_storage::UserPassword::new(password.to_string()); wallet_storage::remove_account_from_login(&login_id, &account_id, &password)?; // Load to reset the internal state @@ -513,7 +517,8 @@ fn derive_address( mnemonic: bip39::Mnemonic, prefix: &str, ) -> Result { - DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)? + // note: the ephemeral wallet will zeroize the mnemonic on drop + DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic) .try_derive_accounts()? .first() .map(AccountData::address) @@ -547,21 +552,20 @@ pub async fn list_accounts( #[tauri::command] pub fn show_mnemonic_for_account_in_password( account_id: String, - password: String, -) -> Result { + password: UserPassword, +) -> Result { log::info!("Getting mnemonic for: {account_id}"); let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id); - let password = wallet_storage::UserPassword::new(password); let mnemonic = _show_mnemonic_for_account_in_password(&login_id, &account_id, &password)?; - Ok(mnemonic.to_string()) + Ok(mnemonic.into_string()) } fn _show_mnemonic_for_account_in_password( login_id: &wallet_storage::LoginId, account_id: &wallet_storage::AccountId, password: &wallet_storage::UserPassword, -) -> Result { +) -> Result { let stored_account = wallet_storage::load_existing_login(login_id, password)?; let mnemonic = match stored_account { wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), @@ -571,12 +575,13 @@ fn _show_mnemonic_for_account_in_password( .mnemonic() .clone(), }; - Ok(mnemonic) + Ok(mnemonic.into()) } #[cfg(test)] mod tests { use std::path::PathBuf; + use std::str::FromStr; use crate::wallet_storage::{ self, @@ -606,8 +611,8 @@ mod tests { let all_accounts: Vec<_> = stored_login .unwrap_into_multiple_accounts(account_id.clone()) - .into_accounts() - .collect(); + .inner() + .to_vec(); assert_eq!( all_accounts, @@ -639,8 +644,8 @@ mod tests { let all_accounts: Vec<_> = stored_login .unwrap_into_multiple_accounts(account_id) - .into_accounts() - .collect(); + .inner() + .to_vec(); let expected_mn2 = bip39::Mnemonic::from_str("border hurt skull lunar goddess second danger game dismiss exhaust oven thumb dog drama onion false orchard spice tent next predict invite cherry green").unwrap(); let expected_mn3 = bip39::Mnemonic::from_str("gentle crowd rule snap girl urge flat jump winner cluster night sand museum stock grunt quick tree acquire traffic major awake tag rack peasant").unwrap(); diff --git a/nym-wallet/src-tauri/src/operations/mixnet/admin.rs b/nym-wallet/src-tauri/src/operations/mixnet/admin.rs index 5074c5500e..d25e9ff8e6 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/admin.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/admin.rs @@ -3,11 +3,11 @@ use crate::error::BackendError; use crate::state::WalletState; -use mixnet_contract_common::ContractStateParams; +use nym_mixnet_contract_common::ContractStateParams; use nym_types::transaction::TransactionExecuteResult; use nym_wallet_types::admin::TauriContractStateParams; -use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; -use validator_client::nymd::Fee; +use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient}; +use validator_client::nyxd::Fee; #[tauri::command] pub async fn get_contract_settings( @@ -17,7 +17,7 @@ pub async fn get_contract_settings( let guard = state.read().await; let reg = guard.registered_coins()?; - let client = &guard.current_client()?.nymd; + let client = &guard.current_client()?.nyxd; let res = client.get_mixnet_contract_settings().await?; let converted = TauriContractStateParams::from_mixnet_contract_contract_state_params(res, reg)?; @@ -33,7 +33,7 @@ pub async fn update_contract_settings( ) -> Result { let guard = state.read().await; let reg = guard.registered_coins()?; - let client = &guard.current_client()?.nymd; + let client = &guard.current_client()?.nyxd; let fee_amount = guard.convert_tx_fee(fee.as_ref()); let mixnet_contract_settings_params: ContractStateParams = diff --git a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs index 567dc84290..216dd12841 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs @@ -3,16 +3,16 @@ use crate::error::BackendError; use crate::state::WalletState; -use crate::{nymd_client, Gateway, MixNode}; -use mixnet_contract_common::{MixId, MixNodeConfigUpdate}; +use crate::{nyxd_client, Gateway, MixNode}; +use nym_mixnet_contract_common::{MixId, MixNodeConfigUpdate}; use nym_types::currency::DecCoin; use nym_types::gateway::GatewayBond; use nym_types::mixnode::{MixNodeCostParams, MixNodeDetails}; use nym_types::transaction::TransactionExecuteResult; use serde::{Deserialize, Serialize}; use std::time::Duration; -use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; -use validator_client::nymd::Fee; +use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient}; +use validator_client::nyxd::Fee; #[derive(Debug, Serialize, Deserialize)] pub struct NodeDescription { @@ -43,7 +43,7 @@ pub async fn bond_gateway( ); let res = guard .current_client()? - .nymd + .nyxd .bond_gateway(gateway, owner_signature, pledge_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -61,7 +61,7 @@ pub async fn unbond_gateway( let guard = state.read().await; let fee_amount = guard.convert_tx_fee(fee.as_ref()); log::info!(">>> Unbond gateway, fee = {:?}", fee); - let res = guard.current_client()?.nymd.unbond_gateway(fee).await?; + let res = guard.current_client()?.nyxd.unbond_gateway(fee).await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( @@ -92,7 +92,7 @@ pub async fn bond_mixnode( ); let res = guard .current_client()? - .nymd + .nyxd .bond_mixnode(mixnode, cost_params, owner_signature, pledge_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -119,7 +119,7 @@ pub async fn pledge_more( ); let res = guard .current_client()? - .nymd + .nyxd .pledge_more(additional_pledge_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -137,7 +137,7 @@ pub async fn unbond_mixnode( let guard = state.read().await; let fee_amount = guard.convert_tx_fee(fee.as_ref()); log::info!(">>> Unbond mixnode, fee = {:?}", fee); - let res = guard.current_client()?.nymd.unbond_mixnode(fee).await?; + let res = guard.current_client()?.nyxd.unbond_mixnode(fee).await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( @@ -162,7 +162,7 @@ pub async fn update_mixnode_cost_params( ); let res = guard .current_client()? - .nymd + .nyxd .update_mixnode_cost_params(cost_params, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -187,7 +187,7 @@ pub async fn update_mixnode_config( ); let res = guard .current_client()? - .nymd + .nyxd .update_mixnode_config(update, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -204,14 +204,14 @@ pub async fn get_mixnode_avg_uptime( log::info!(">>> Get mixnode bond details"); let guard = state.read().await; let client = guard.current_client()?; - let res = client.nymd.get_owned_mixnode(client.nymd.address()).await?; + let res = client.nyxd.get_owned_mixnode(client.nyxd.address()).await?; match res.mixnode_details { Some(details) => { let id = details.mix_id(); log::trace!(" >>> Get average uptime percentage: mix_id = {}", id); let avg_uptime_percent = client - .validator_api + .nym_api .get_mixnode_avg_uptime(id) .await .ok() @@ -230,7 +230,7 @@ pub async fn mixnode_bond_details( log::info!(">>> Get mixnode bond details"); let guard = state.read().await; let client = guard.current_client()?; - let res = client.nymd.get_owned_mixnode(client.nymd.address()).await?; + let res = client.nyxd.get_owned_mixnode(client.nyxd.address()).await?; let details = res .mixnode_details .map(|details| { @@ -258,7 +258,7 @@ pub async fn gateway_bond_details( log::info!(">>> Get gateway bond details"); let guard = state.read().await; let client = guard.current_client()?; - let bond = client.nymd.get_owned_gateway(client.nymd.address()).await?; + let bond = client.nyxd.get_owned_gateway(client.nyxd.address()).await?; let res = bond .gateway .map(|bond| { @@ -286,7 +286,7 @@ pub async fn get_pending_operator_rewards( let guard = state.read().await; let res = guard .current_client()? - .nymd + .nyxd .get_pending_operator_reward(&address.parse()?) .await?; @@ -319,7 +319,7 @@ pub async fn get_number_of_mixnode_delegators( mix_id: MixId, state: tauri::State<'_, WalletState>, ) -> Result { - Ok(nymd_client!(state) + Ok(nyxd_client!(state) .get_mixnode_details(mix_id) .await? .mixnode_details @@ -335,9 +335,24 @@ pub async fn get_mix_node_description( Ok(reqwest::Client::builder() .timeout(Duration::from_millis(1000)) .build()? - .get(format!("http://{}:{}/description", host, port)) + .get(format!("http://{host}:{port}/description")) .send() .await? .json() .await?) } + +#[tauri::command] +pub async fn get_mixnode_uptime( + mix_id: MixId, + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Get mixnode uptime"); + + let guard = state.read().await; + let client = guard.current_client()?; + let uptime = client.nym_api.get_mixnode_avg_uptime(mix_id).await?; + + log::info!(">>> Uptime response: {}", uptime.avg_uptime); + Ok(uptime.avg_uptime) +} diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index dfbf46389b..6d54ee878d 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -4,7 +4,7 @@ use crate::error::BackendError; use crate::state::WalletState; use crate::vesting::delegate::vesting_undelegate_from_mixnode; -use mixnet_contract_common::MixId; +use nym_mixnet_contract_common::MixId; use nym_types::currency::DecCoin; use nym_types::delegation::{Delegation, DelegationWithEverything, DelegationsSummaryResponse}; use nym_types::deprecated::{ @@ -13,8 +13,8 @@ use nym_types::deprecated::{ use nym_types::mixnode::MixNodeCostParams; use nym_types::pending_events::PendingEpochEvent; use nym_types::transaction::TransactionExecuteResult; -use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; -use validator_client::nymd::Fee; +use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient}; +use validator_client::nyxd::Fee; #[tauri::command] pub async fn get_pending_delegation_events( @@ -25,7 +25,7 @@ pub async fn get_pending_delegation_events( let reg = guard.registered_coins()?; let client = guard.current_client()?; - let events = client.get_all_nymd_pending_epoch_events().await?; + let events = client.get_all_nyxd_pending_epoch_events().await?; let converted = events .into_iter() .map(|e| PendingEpochEvent::try_from_mixnet_contract(e, reg)) @@ -36,9 +36,9 @@ pub async fn get_pending_delegation_events( // we only care about events concerning THIS client let mut client_specific_events = Vec::new(); for delegation_event in delegation_events { - if delegation_event.address_matches(client.nymd.address().as_ref()) { + if delegation_event.address_matches(client.nyxd.address().as_ref()) { let node_identity = client - .nymd + .nyxd .get_mixnode_details(delegation_event.mix_id) .await? .mixnode_details @@ -82,7 +82,7 @@ pub async fn delegate_to_mixnode( fee, ); let res = client - .nymd + .nyxd .delegate_to_mixnode(mix_id, delegation_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -108,7 +108,7 @@ pub async fn undelegate_from_mixnode( ); let res = guard .current_client()? - .nymd + .nyxd .undelegate_from_mixnode(mix_id, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -153,10 +153,10 @@ pub async fn get_all_mix_delegations( let client = guard.current_client()?; let reg = guard.registered_coins()?; - let address = client.nymd.address(); + let address = client.nyxd.address(); let network = guard.current_network(); let base_mix_denom = network.base_mix_denom().to_string(); - let vesting_contract = client.nymd.vesting_contract_address(); + let vesting_contract = client.nyxd.vesting_contract_address(); log::info!(" >>> Get delegations"); let delegations = client.get_all_delegator_delegations(address).await?; @@ -213,7 +213,7 @@ pub async fn get_all_mix_delegations( log::trace!(" >>> Get accumulated rewards: address = {}", address); let pending_reward = client - .nymd + .nyxd .get_pending_delegator_reward(address, d.mix_id, d.proxy.clone()) .await?; @@ -230,7 +230,7 @@ pub async fn get_all_mix_delegations( }; log::trace!(" >>> Get stake saturation: mix_id = {}", d.mix_id); - let stake_saturation = client.nymd.get_mixnode_stake_saturation(d.mix_id).await?; + let stake_saturation = client.nyxd.get_mixnode_stake_saturation(d.mix_id).await?; log::trace!(" <<< {:?}", stake_saturation); log::trace!( @@ -238,7 +238,7 @@ pub async fn get_all_mix_delegations( d.mix_id ); let avg_uptime_percent = client - .validator_api + .nym_api .get_mixnode_avg_uptime(d.mix_id) .await .ok() @@ -250,7 +250,7 @@ pub async fn get_all_mix_delegations( d.height ); let timestamp = client - .nymd + .nyxd .get_block_timestamp(Some(d.height as u32)) .await?; let delegated_on_iso_datetime = timestamp.to_rfc3339(); @@ -325,7 +325,7 @@ pub async fn get_pending_delegator_rewards( let guard = state.read().await; let res = guard .current_client()? - .nymd + .nyxd .get_pending_delegator_reward(&address.parse()?, mix_id, proxy) .await?; diff --git a/nym-wallet/src-tauri/src/operations/mixnet/families.rs b/nym-wallet/src-tauri/src/operations/mixnet/families.rs index 6e391ab722..aba66c9a58 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/families.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/families.rs @@ -1,8 +1,8 @@ use crate::error::BackendError; use crate::state::WalletState; use nym_types::transaction::TransactionExecuteResult; -use validator_client::nymd::traits::MixnetSigningClient; -use validator_client::nymd::Fee; +use validator_client::nyxd::traits::MixnetSigningClient; +use validator_client::nyxd::Fee; #[tauri::command] pub async fn create_family( @@ -15,7 +15,7 @@ pub async fn create_family( let fee_amount = guard.convert_tx_fee(fee.as_ref()); let res = guard .current_client()? - .nymd + .nyxd .create_family(signature, label, fee) .await?; Ok(TransactionExecuteResult::from_execute_result( @@ -34,7 +34,7 @@ pub async fn join_family( let fee_amount = guard.convert_tx_fee(fee.as_ref()); let res = guard .current_client()? - .nymd + .nyxd .join_family(signature, family_head, fee) .await?; Ok(TransactionExecuteResult::from_execute_result( @@ -53,7 +53,7 @@ pub async fn leave_family( let fee_amount = guard.convert_tx_fee(fee.as_ref()); let res = guard .current_client()? - .nymd + .nyxd .leave_family(signature, family_head, fee) .await?; Ok(TransactionExecuteResult::from_execute_result( @@ -72,7 +72,7 @@ pub async fn kick_family_member( let fee_amount = guard.convert_tx_fee(fee.as_ref()); let res = guard .current_client()? - .nymd + .nyxd .kick_family_member(signature, member, fee) .await?; Ok(TransactionExecuteResult::from_execute_result( diff --git a/nym-wallet/src-tauri/src/operations/mixnet/interval.rs b/nym-wallet/src-tauri/src/operations/mixnet/interval.rs index ce88d840b4..996f9bbf69 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/interval.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/interval.rs @@ -2,18 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::BackendError; -use crate::nymd_client; +use crate::nyxd_client; use crate::state::WalletState; use nym_types::pending_events::{PendingEpochEvent, PendingIntervalEvent}; use nym_wallet_types::interval::Interval; -use validator_client::nymd::traits::MixnetQueryClient; +use validator_client::nyxd::traits::MixnetQueryClient; #[tauri::command] pub async fn get_current_interval( state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Get current interval"); - let res = nymd_client!(state).get_current_interval_details().await?; + let res = nyxd_client!(state).get_current_interval_details().await?; log::info!("<<< current interval = {:?}", res); Ok(res.interval.into()) } @@ -26,7 +26,7 @@ pub async fn get_pending_epoch_events( let guard = state.read().await; let reg = guard.registered_coins()?; let client = guard.current_client()?; - let res = client.get_all_nymd_pending_epoch_events().await?; + let res = client.get_all_nyxd_pending_epoch_events().await?; log::info!("<<< got = {:?} events", res.len()); @@ -46,7 +46,7 @@ pub async fn get_pending_interval_events( let guard = state.read().await; let reg = guard.registered_coins()?; let client = guard.current_client()?; - let res = client.get_all_nymd_pending_interval_events().await?; + let res = client.get_all_nyxd_pending_interval_events().await?; log::info!("<<< got = {:?} events", res.len()); diff --git a/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs b/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs index 6fb8893ee9..18a680b985 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs @@ -1,10 +1,10 @@ use crate::error::BackendError; use crate::state::WalletState; use crate::vesting::rewards::vesting_claim_delegator_reward; -use mixnet_contract_common::MixId; +use nym_mixnet_contract_common::{MixId, RewardingParams}; use nym_types::transaction::TransactionExecuteResult; -use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; -use validator_client::nymd::Fee; +use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient}; +use validator_client::nyxd::Fee; #[tauri::command] pub async fn claim_operator_reward( @@ -17,7 +17,7 @@ pub async fn claim_operator_reward( let fee_amount = guard.convert_tx_fee(fee.as_ref()); let res = guard .current_client()? - .nymd + .nyxd .withdraw_operator_reward(fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -38,7 +38,7 @@ pub async fn claim_delegator_reward( let fee_amount = guard.convert_tx_fee(fee.as_ref()); let res = guard .current_client()? - .nymd + .nyxd .withdraw_delegator_reward(mix_id, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -63,11 +63,11 @@ pub async fn claim_locked_and_unlocked_delegator_reward( let client = guard.current_client()?; log::trace!(">>> Get delegations: mix_id = {}", mix_id); - let address = client.nymd.address(); + let address = client.nyxd.address(); let delegations = client.get_all_delegator_delegations(address).await?; log::trace!("<<< {} delegations", delegations.len()); - let vesting_contract = client.nymd.vesting_contract_address().to_string(); + let vesting_contract = client.nyxd.vesting_contract_address().to_string(); let liquid_delegation = client.get_delegation_details(mix_id, address, None).await?; let vesting_delegation = client .get_delegation_details(mix_id, address, Some(vesting_contract)) @@ -95,3 +95,17 @@ pub async fn claim_locked_and_unlocked_delegator_reward( log::trace!("<<< {:?}", res); Ok(res) } + +#[tauri::command] +pub async fn get_current_rewarding_parameters( + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Get current rewarding params",); + + let guard = state.read().await; + let client = guard.current_client()?; + + let reward_params = client.nyxd.get_rewarding_parameters().await?; + + Ok(reward_params) +} diff --git a/nym-wallet/src-tauri/src/operations/mixnet/send.rs b/nym-wallet/src-tauri/src/operations/mixnet/send.rs index 0e4cbdd6a3..b57cf0a267 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/send.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/send.rs @@ -3,7 +3,7 @@ use crate::state::WalletState; use nym_types::currency::DecCoin; use nym_types::transaction::{SendTxResult, TransactionDetails}; use std::str::FromStr; -use validator_client::nymd::{AccountId, Fee}; +use validator_client::nyxd::{AccountId, Fee}; #[tauri::command] pub async fn send( @@ -17,7 +17,7 @@ pub async fn send( let amount_base = guard.attempt_convert_to_base_coin(amount.clone())?; let to_address = AccountId::from_str(address)?; - let from_address = guard.current_client()?.nymd.address().to_string(); + let from_address = guard.current_client()?.nyxd.address().to_string(); let fee_amount = guard.convert_tx_fee(fee.as_ref()); log::info!( ">>> Send: display_amount = {}, base_amount = {}, from = {}, to = {}, fee = {:?}", @@ -29,7 +29,7 @@ pub async fn send( ); let raw_res = guard .current_client()? - .nymd + .nyxd .send(&to_address, vec![amount_base], memo, fee) .await?; log::info!("<<< tx hash = {}", raw_res.hash.to_string()); diff --git a/nym-wallet/src-tauri/src/operations/mod.rs b/nym-wallet/src-tauri/src/operations/mod.rs index 457971d8c8..c6c175ee12 100644 --- a/nym-wallet/src-tauri/src/operations/mod.rs +++ b/nym-wallet/src-tauri/src/operations/mod.rs @@ -1,6 +1,6 @@ pub mod help; pub mod mixnet; +pub mod nym_api; pub mod signatures; pub mod simulate; -pub mod validator_api; pub mod vesting; diff --git a/nym-wallet/src-tauri/src/operations/validator_api/mod.rs b/nym-wallet/src-tauri/src/operations/nym_api/mod.rs similarity index 100% rename from nym-wallet/src-tauri/src/operations/validator_api/mod.rs rename to nym-wallet/src-tauri/src/operations/nym_api/mod.rs diff --git a/nym-wallet/src-tauri/src/operations/validator_api/status.rs b/nym-wallet/src-tauri/src/operations/nym_api/status.rs similarity index 66% rename from nym-wallet/src-tauri/src/operations/validator_api/status.rs rename to nym-wallet/src-tauri/src/operations/nym_api/status.rs index 16c83cfb67..257fcd798f 100644 --- a/nym-wallet/src-tauri/src/operations/validator_api/status.rs +++ b/nym-wallet/src-tauri/src/operations/nym_api/status.rs @@ -4,11 +4,13 @@ use crate::api_client; use crate::error::BackendError; use crate::state::WalletState; -use mixnet_contract_common::{IdentityKeyRef, MixId}; +use nym_mixnet_contract_common::{ + reward_params::Performance, Coin, IdentityKeyRef, MixId, Percent, +}; use validator_client::models::{ - GatewayCoreStatusResponse, GatewayStatusReportResponse, InclusionProbabilityResponse, - MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, - StakeSaturationResponse, + ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse, + InclusionProbabilityResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse, + RewardEstimationResponse, StakeSaturationResponse, }; #[tauri::command] @@ -59,6 +61,29 @@ pub async fn mixnode_reward_estimation( .await?) } +#[tauri::command] +pub async fn compute_mixnode_reward_estimation( + mix_id: u32, + performance: Option, + pledge_amount: Option, + total_delegation: Option, + interval_operating_cost: Option, + profit_margin_percent: Option, + state: tauri::State<'_, WalletState>, +) -> Result { + let request_body = ComputeRewardEstParam { + performance, + active_in_rewarded_set: Some(true), + pledge_amount, + total_delegation, + interval_operating_cost, + profit_margin_percent, + }; + Ok(api_client!(state) + .compute_mixnode_reward_estimation(mix_id, &request_body) + .await?) +} + #[tauri::command] pub async fn mixnode_stake_saturation( mix_id: MixId, diff --git a/nym-wallet/src-tauri/src/operations/signatures/sign.rs b/nym-wallet/src-tauri/src/operations/signatures/sign.rs index abb132c1c8..b996887e51 100644 --- a/nym-wallet/src-tauri/src/operations/signatures/sign.rs +++ b/nym-wallet/src-tauri/src/operations/signatures/sign.rs @@ -23,7 +23,7 @@ pub async fn sign( ) -> Result { let guard = state.read().await; let client = guard.current_client()?; - let wallet = client.nymd.signer(); + let wallet = client.nyxd.signer(); let derived_accounts = wallet.try_derive_accounts()?; let account = derived_accounts.first().ok_or_else(|| { log::error!(">>> Unable to derive account"); @@ -50,18 +50,18 @@ async fn get_pubkey_from_account_address( let guard = state.read().await; let client = guard.current_client()?; let account = client - .nymd + .nyxd .get_account_details(address) .await? .ok_or_else(|| { log::error!("No account associated with address {}", address); - BackendError::SignatureError(format!("No account associated with address {}", address)) + BackendError::SignatureError(format!("No account associated with address {address}")) })?; let base_account = account.try_get_base_account()?; base_account.pubkey.ok_or_else(|| { log::error!("No pubkey found for address {}", address); - BackendError::SignatureError(format!("No pubkey found for address {}", address)) + BackendError::SignatureError(format!("No pubkey found for address {address}")) }) } @@ -116,7 +116,7 @@ pub async fn verify( // get public key from current account address let guard = state.read().await; let client = guard.current_client()?; - let address = client.nymd.address(); + let address = client.nyxd.address(); get_pubkey_from_account_address(address, &state).await? } }; diff --git a/nym-wallet/src-tauri/src/operations/simulate/admin.rs b/nym-wallet/src-tauri/src/operations/simulate/admin.rs index 29636bebf2..f47b44e454 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/admin.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/admin.rs @@ -4,7 +4,7 @@ use crate::error::BackendError; use crate::operations::simulate::FeeDetails; use crate::WalletState; -use mixnet_contract_common::{ContractStateParams, ExecuteMsg}; +use nym_mixnet_contract_common::{ContractStateParams, ExecuteMsg}; use nym_wallet_types::admin::TauriContractStateParams; #[tauri::command] @@ -18,9 +18,9 @@ pub async fn simulate_update_contract_settings( params.try_convert_to_mixnet_contract_params(reg)?; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address(); + let mixnet_contract = client.nyxd.mixnet_contract_address(); - let msg = client.nymd.wrap_contract_execute_message( + let msg = client.nyxd.wrap_contract_execute_message( mixnet_contract, &ExecuteMsg::UpdateContractStateParams { updated_parameters: mixnet_contract_settings_params, @@ -28,6 +28,6 @@ pub async fn simulate_update_contract_settings( vec![], )?; - let result = client.nymd.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 2796e02097..a83aaf618a 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs @@ -6,7 +6,7 @@ use crate::operations::simulate::FeeDetails; use crate::state::WalletState; use nym_types::currency::DecCoin; use std::str::FromStr; -use validator_client::nymd::{AccountId, MsgSend}; +use validator_client::nyxd::{AccountId, MsgSend}; #[tauri::command] pub async fn simulate_send( @@ -21,7 +21,7 @@ pub async fn simulate_send( let amount = vec![amount_base.into()]; let client = guard.current_client()?; - let from_address = client.nymd.address().clone(); + let from_address = client.nyxd.address().clone(); // TODO: I'm still not 100% convinced whether this should be exposed here or handled somewhere else in the client code let msg = MsgSend { @@ -30,6 +30,16 @@ pub async fn simulate_send( amount, }; - let result = client.nymd.simulate(vec![msg]).await?; + let result = client.nyxd.simulate(vec![msg]).await?; guard.create_detailed_fee(result) } + +#[tauri::command] +pub async fn get_custom_fees( + fees_amount: DecCoin, + state: tauri::State<'_, WalletState>, +) -> Result { + let guard = state.read().await; + let fee = guard.attempt_convert_to_fixed_fee(fees_amount.clone())?; + Ok(FeeDetails::new(Some(fees_amount), fee)) +} diff --git a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs index 33e649690b..a971fea215 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs @@ -4,8 +4,8 @@ use crate::error::BackendError; use crate::operations::simulate::FeeDetails; use crate::WalletState; -use mixnet_contract_common::MixNodeConfigUpdate; -use mixnet_contract_common::{ExecuteMsg, Gateway, MixId, MixNode}; +use nym_mixnet_contract_common::MixNodeConfigUpdate; +use nym_mixnet_contract_common::{ExecuteMsg, Gateway, MixId, MixNode}; use nym_types::currency::DecCoin; use nym_types::mixnode::MixNodeCostParams; @@ -25,13 +25,13 @@ async fn simulate_mixnet_operation( }; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address(); + let mixnet_contract = client.nyxd.mixnet_contract_address(); let msg = client - .nymd + .nyxd .wrap_contract_execute_message(mixnet_contract, &msg, funds)?; - let result = client.nymd.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/mod.rs b/nym-wallet/src-tauri/src/operations/simulate/mod.rs index 256f6f9a84..384b8b9085 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mod.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mod.rs @@ -4,8 +4,8 @@ use cosmrs::tx; use cosmrs::tx::Gas; use nym_types::fees::FeeDetails; -use validator_client::nymd::cosmwasm_client::types::GasInfo; -use validator_client::nymd::{CosmosCoin, Fee, GasAdjustable, GasAdjustment, GasPrice}; +use validator_client::nyxd::cosmwasm_client::types::GasInfo; +use validator_client::nyxd::{CosmosCoin, Fee, GasAdjustable, GasAdjustment, GasPrice}; pub mod admin; pub mod cosmos; diff --git a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs index 213c8d3fd8..b0616bb0ef 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs @@ -4,11 +4,11 @@ use crate::error::BackendError; use crate::operations::simulate::FeeDetails; use crate::WalletState; -use mixnet_contract_common::MixNodeConfigUpdate; -use mixnet_contract_common::{Gateway, MixId, MixNode}; +use nym_mixnet_contract_common::MixNodeConfigUpdate; +use nym_mixnet_contract_common::{Gateway, MixId, MixNode}; use nym_types::currency::DecCoin; use nym_types::mixnode::MixNodeCostParams; -use vesting_contract_common::ExecuteMsg; +use nym_vesting_contract_common::ExecuteMsg; async fn simulate_vesting_operation( msg: ExecuteMsg, @@ -26,13 +26,13 @@ async fn simulate_vesting_operation( }; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address(); + let vesting_contract = client.nyxd.vesting_contract_address(); let msg = client - .nymd + .nyxd .wrap_contract_execute_message(vesting_contract, &msg, funds)?; - let result = client.nymd.simulate(vec![msg]).await?; + let result = client.nyxd.simulate(vec![msg]).await?; guard.create_detailed_fee(result) } @@ -151,7 +151,11 @@ pub async fn simulate_vesting_delegate_to_mixnode( let amount = guard.attempt_convert_to_base_coin(amount)?.into(); simulate_vesting_operation( - ExecuteMsg::DelegateToMixnode { mix_id, amount }, + ExecuteMsg::DelegateToMixnode { + on_behalf_of: None, + mix_id, + amount, + }, None, &state, ) @@ -163,7 +167,15 @@ pub async fn simulate_vesting_undelegate_from_mixnode( mix_id: MixId, state: tauri::State<'_, WalletState>, ) -> Result { - simulate_vesting_operation(ExecuteMsg::UndelegateFromMixnode { mix_id }, None, &state).await + simulate_vesting_operation( + ExecuteMsg::UndelegateFromMixnode { + on_behalf_of: None, + mix_id, + }, + None, + &state, + ) + .await } #[tauri::command] diff --git a/nym-wallet/src-tauri/src/operations/vesting/bond.rs b/nym-wallet/src-tauri/src/operations/vesting/bond.rs index da5a71eee1..c884be5538 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/bond.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/bond.rs @@ -1,13 +1,13 @@ use crate::error::BackendError; -use crate::nymd_client; +use crate::nyxd_client; use crate::state::WalletState; use crate::{Gateway, MixNode}; -use mixnet_contract_common::MixNodeConfigUpdate; +use nym_mixnet_contract_common::MixNodeConfigUpdate; use nym_types::currency::DecCoin; use nym_types::mixnode::MixNodeCostParams; use nym_types::transaction::TransactionExecuteResult; -use validator_client::nymd::{Fee, VestingSigningClient}; +use validator_client::nyxd::{Fee, VestingSigningClient}; #[tauri::command] pub async fn vesting_bond_gateway( @@ -30,7 +30,7 @@ pub async fn vesting_bond_gateway( ); let res = guard .current_client()? - .nymd + .nyxd .vesting_bond_gateway(gateway, &owner_signature, pledge_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -51,7 +51,7 @@ pub async fn vesting_unbond_gateway( ">>> Unbond gateway bonded with locked tokens, fee = {:?}", fee ); - let res = nymd_client!(state).vesting_unbond_gateway(fee).await?; + let res = nyxd_client!(state).vesting_unbond_gateway(fee).await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( @@ -83,7 +83,7 @@ pub async fn vesting_bond_mixnode( ); let res = guard .current_client()? - .nymd + .nyxd .vesting_bond_mixnode(mixnode, cost_params, &owner_signature, pledge_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -110,7 +110,7 @@ pub async fn vesting_pledge_more( ); let res = guard .current_client()? - .nymd + .nyxd .vesting_pledge_more(additional_pledge_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -133,7 +133,7 @@ pub async fn vesting_unbond_mixnode( ); let res = guard .current_client()? - .nymd + .nyxd .vesting_unbond_mixnode(fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -161,7 +161,7 @@ pub async fn withdraw_vested_coins( ); let res = guard .current_client()? - .nymd + .nyxd .withdraw_vested_coins(amount_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -189,7 +189,7 @@ pub async fn vesting_update_mixnode_cost_params( ); let res = guard .current_client()? - .nymd + .nyxd .vesting_update_mixnode_cost_params(cost_params, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -214,7 +214,7 @@ pub async fn vesting_update_mixnode_config( ); let res = guard .current_client()? - .nymd + .nyxd .vesting_update_mixnode_config(update, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); diff --git a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs index 59183878e5..53e3822e67 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs @@ -3,10 +3,10 @@ use crate::error::BackendError; use crate::state::WalletState; -use mixnet_contract_common::MixId; +use nym_mixnet_contract_common::MixId; use nym_types::currency::DecCoin; use nym_types::transaction::TransactionExecuteResult; -use validator_client::nymd::{Fee, VestingSigningClient}; +use validator_client::nyxd::{Fee, VestingSigningClient}; #[tauri::command] pub async fn vesting_delegate_to_mixnode( @@ -28,8 +28,8 @@ pub async fn vesting_delegate_to_mixnode( ); let res = guard .current_client()? - .nymd - .vesting_delegate_to_mixnode(mix_id, delegation, fee) + .nyxd + .vesting_delegate_to_mixnode(mix_id, delegation, None, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); @@ -53,8 +53,8 @@ pub async fn vesting_undelegate_from_mixnode( ); let res = guard .current_client()? - .nymd - .vesting_undelegate_from_mixnode(mix_id, fee) + .nyxd + .vesting_undelegate_from_mixnode(mix_id, None, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); diff --git a/nym-wallet/src-tauri/src/operations/vesting/queries.rs b/nym-wallet/src-tauri/src/operations/vesting/queries.rs index d05608fb25..72ce47e9ef 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/queries.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/queries.rs @@ -2,17 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::BackendError; -use crate::nymd_client; +use crate::nyxd_client; use crate::state::WalletState; use cosmwasm_std::Timestamp; use nym_types::currency::DecCoin; use nym_types::vesting::VestingAccountInfo; use nym_types::vesting::{OriginalVestingResponse, PledgeData}; -use validator_client::nymd::VestingQueryClient; -use vesting_contract_common::Period; +use nym_vesting_contract_common::Period; +use validator_client::nyxd::VestingQueryClient; #[tauri::command] -pub async fn locked_coins( +pub(crate) async fn locked_coins( block_time: Option, state: tauri::State<'_, WalletState>, ) -> Result { @@ -21,19 +21,19 @@ pub async fn locked_coins( let client = guard.current_client()?; let res = client - .nymd + .nyxd .locked_coins( - client.nymd.address().as_ref(), + client.nyxd.address().as_ref(), block_time.map(Timestamp::from_seconds), ) .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< locked coins = {}", display); + log::info!("<<< locked coins = {display}"); Ok(display) } #[tauri::command] -pub async fn spendable_coins( +pub(crate) async fn spendable_coins( block_time: Option, state: tauri::State<'_, WalletState>, ) -> Result { @@ -42,20 +42,56 @@ pub async fn spendable_coins( let client = guard.current_client()?; let res = client - .nymd + .nyxd .spendable_coins( - client.nymd.address().as_ref(), + client.nyxd.address().as_ref(), block_time.map(Timestamp::from_seconds), ) .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< spendable coins = {}", display); + log::info!("<<< spendable coins = {display}"); Ok(display) } #[tauri::command] -pub async fn vested_coins( +pub(crate) async fn spendable_vested_coins( + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Query spendable vested coins"); + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nyxd + .get_spendable_vested_coins(client.nyxd.address().as_ref()) + .await?; + + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< spendable vested coins = {display}"); + Ok(display) +} + +#[tauri::command] +pub(crate) async fn spendable_reward_coins( + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Query spendable reward coins"); + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nyxd + .get_spendable_reward_coins(client.nyxd.address().as_ref()) + .await?; + + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< spendable reward coins = {display}"); + Ok(display) +} + +#[tauri::command] +pub(crate) async fn vested_coins( vesting_account_address: &str, block_time: Option, state: tauri::State<'_, WalletState>, @@ -65,7 +101,7 @@ pub async fn vested_coins( let res = guard .current_client()? - .nymd + .nyxd .vested_coins( vesting_account_address, block_time.map(Timestamp::from_seconds), @@ -73,12 +109,12 @@ pub async fn vested_coins( .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< vested coins = {}", display); + log::info!("<<< vested coins = {display}"); Ok(display) } #[tauri::command] -pub async fn vesting_coins( +pub(crate) async fn vesting_coins( vesting_account_address: &str, block_time: Option, state: tauri::State<'_, WalletState>, @@ -88,7 +124,7 @@ pub async fn vesting_coins( let res = guard .current_client()? - .nymd + .nyxd .vesting_coins( vesting_account_address, block_time.map(Timestamp::from_seconds), @@ -96,17 +132,17 @@ pub async fn vesting_coins( .await?; let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< vesting coins = {}", display); + log::info!("<<< vesting coins = {display}"); Ok(display) } #[tauri::command] -pub async fn vesting_start_time( +pub(crate) async fn vesting_start_time( vesting_account_address: &str, state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Query vesting start time"); - let res = nymd_client!(state) + let res = nyxd_client!(state) .vesting_start_time(vesting_account_address) .await? .seconds(); @@ -115,12 +151,12 @@ pub async fn vesting_start_time( } #[tauri::command] -pub async fn vesting_end_time( +pub(crate) async fn vesting_end_time( vesting_account_address: &str, state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Query vesting end time"); - let res = nymd_client!(state) + let res = nyxd_client!(state) .vesting_end_time(vesting_account_address) .await? .seconds(); @@ -129,7 +165,7 @@ pub async fn vesting_end_time( } #[tauri::command] -pub async fn original_vesting( +pub(crate) async fn original_vesting( vesting_account_address: &str, state: tauri::State<'_, WalletState>, ) -> Result { @@ -139,7 +175,7 @@ pub async fn original_vesting( let res = guard .current_client()? - .nymd + .nyxd .original_vesting(vesting_account_address) .await?; @@ -149,54 +185,153 @@ pub async fn original_vesting( } #[tauri::command] -pub async fn delegated_free( - vesting_account_address: &str, - block_time: Option, +pub(crate) async fn get_historical_vesting_staking_reward( state: tauri::State<'_, WalletState>, ) -> Result { - log::info!(">>> Query delegated free"); + log::info!(">>> Query historical vesting staking reward coins"); let guard = state.read().await; + let client = guard.current_client()?; - let res = guard - .current_client()? - .nymd - .delegated_free( - vesting_account_address, - block_time.map(Timestamp::from_seconds), - ) + let res = client + .nyxd + .get_historical_vesting_staking_reward(client.nyxd.address().as_ref()) .await?; - let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< delegated free = {}", display); + log::info!("<<< historical vesting staking reward coins = {display}"); Ok(display) } +#[tauri::command] +pub(crate) async fn get_spendable_vested_coins( + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Query spendable vested coins"); + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nyxd + .get_spendable_vested_coins(client.nyxd.address().as_ref()) + .await?; + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< spendable vested coins = {display}"); + Ok(display) +} + +#[tauri::command] +pub(crate) async fn get_spendable_reward_coins( + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Query spendable reward coins"); + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nyxd + .get_spendable_reward_coins(client.nyxd.address().as_ref()) + .await?; + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< spendable reward coins = {display}"); + Ok(display) +} + +#[tauri::command] +pub(crate) async fn get_delegated_coins( + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Query delegated coins"); + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nyxd + .get_delegated_coins(client.nyxd.address().as_ref()) + .await?; + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< delegated coins = {display}"); + Ok(display) +} + +#[tauri::command] +pub(crate) async fn get_pledged_coins( + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Query pledged coins"); + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nyxd + .get_pledged_coins(client.nyxd.address().as_ref()) + .await?; + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< pledged coins = {display}"); + Ok(display) +} + +#[tauri::command] +pub(crate) async fn get_staked_coins( + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Query staked coins"); + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nyxd + .get_staked_coins(client.nyxd.address().as_ref()) + .await?; + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< staked coins = {display}"); + Ok(display) +} + +#[tauri::command] +pub(crate) async fn get_withdrawn_coins( + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Query withdrawn coins"); + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nyxd + .get_withdrawn_coins(client.nyxd.address().as_ref()) + .await?; + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< pledged coins = {display}"); + Ok(display) +} + +#[tauri::command] +pub(crate) async fn delegated_free( + _vesting_account_address: &str, + _block_time: Option, + _state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Query delegated free -> THIS QUERY HAS BEEN REMOVED FROM THE CONTRACT"); + Err(BackendError::RemovedCommand { + name: "vesting::queries::delegated_free".to_string(), + alternative: "vesting::queries::get_delegated_coins".to_string(), + }) +} + /// Returns the total amount of delegated tokens that have vested #[tauri::command] -pub async fn delegated_vesting( - block_time: Option, - vesting_account_address: &str, - state: tauri::State<'_, WalletState>, +pub(crate) async fn delegated_vesting( + _block_time: Option, + _vesting_account_address: &str, + _state: tauri::State<'_, WalletState>, ) -> Result { - log::info!(">>> Query delegated vesting"); - let guard = state.read().await; - - let res = guard - .current_client()? - .nymd - .delegated_vesting( - vesting_account_address, - block_time.map(Timestamp::from_seconds), - ) - .await?; - - let display = guard.attempt_convert_to_display_dec_coin(res)?; - log::info!("<<< delegated_vesting = {}", display); - Ok(display) + log::info!(">>> Query delegated vesting -> THIS QUERY HAS BEEN REMOVED FROM THE CONTRACT"); + Err(BackendError::RemovedCommand { + name: "vesting::queries::delegated_vesting".to_string(), + alternative: "vesting::queries::get_delegated_coins".to_string(), + }) } #[tauri::command] -pub async fn vesting_get_mixnode_pledge( +pub(crate) async fn vesting_get_mixnode_pledge( address: &str, state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { @@ -206,7 +341,7 @@ pub async fn vesting_get_mixnode_pledge( let res = guard .current_client()? - .nymd + .nyxd .get_mixnode_pledge(address) .await? .map(|pledge| PledgeData::from_vesting_contract(pledge, reg)) @@ -217,7 +352,7 @@ pub async fn vesting_get_mixnode_pledge( } #[tauri::command] -pub async fn vesting_get_gateway_pledge( +pub(crate) async fn vesting_get_gateway_pledge( address: &str, state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { @@ -227,7 +362,7 @@ pub async fn vesting_get_gateway_pledge( let res = guard .current_client()? - .nymd + .nyxd .get_gateway_pledge(address) .await? .map(|pledge| PledgeData::from_vesting_contract(pledge, reg)) @@ -238,12 +373,12 @@ pub async fn vesting_get_gateway_pledge( } #[tauri::command] -pub async fn get_current_vesting_period( +pub(crate) async fn get_current_vesting_period( address: &str, state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Query current vesting period"); - let res = nymd_client!(state) + let res = nyxd_client!(state) .get_current_vesting_period(address) .await?; log::info!("<<< {:?}", res); @@ -251,7 +386,7 @@ pub async fn get_current_vesting_period( } #[tauri::command] -pub async fn get_account_info( +pub(crate) async fn get_account_info( address: &str, state: tauri::State<'_, WalletState>, ) -> Result { @@ -259,7 +394,7 @@ pub async fn get_account_info( let guard = state.read().await; let res = guard.registered_coins()?; - let vesting_account = guard.current_client()?.nymd.get_account(address).await?; + let vesting_account = guard.current_client()?.nyxd.get_account(address).await?; let res = VestingAccountInfo::from_vesting_contract(vesting_account, res)?; log::info!("<<< {:?}", res); diff --git a/nym-wallet/src-tauri/src/operations/vesting/rewards.rs b/nym-wallet/src-tauri/src/operations/vesting/rewards.rs index ad3760a364..e3f42ba7a7 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/rewards.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/rewards.rs @@ -3,9 +3,9 @@ use crate::error::BackendError; use crate::state::WalletState; -use mixnet_contract_common::MixId; +use nym_mixnet_contract_common::MixId; use nym_types::transaction::TransactionExecuteResult; -use validator_client::nymd::Fee; +use validator_client::nyxd::Fee; #[tauri::command] pub async fn vesting_claim_operator_reward( @@ -17,7 +17,7 @@ pub async fn vesting_claim_operator_reward( let fee_amount = guard.convert_tx_fee(fee.as_ref()); let res = guard .current_client()? - .nymd + .nyxd .execute_vesting_withdraw_operator_reward(None) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); @@ -41,7 +41,7 @@ pub async fn vesting_claim_delegator_reward( let fee_amount = guard.convert_tx_fee(fee.as_ref()); let res = guard .current_client()? - .nymd + .nyxd .execute_vesting_withdraw_delegator_reward(mix_id, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index e36dfe1b5c..6e9474d3a4 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -1,7 +1,7 @@ use crate::config; use crate::error::BackendError; use crate::simulate::SimulateResult; -use ::config::defaults::NymNetworkDetails; +use ::nym_config::defaults::NymNetworkDetails; use cosmwasm_std::Decimal; use itertools::Itertools; use log::warn; @@ -16,8 +16,8 @@ use std::time::Duration; use strum::IntoEnumIterator; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use url::Url; -use validator_client::nymd::cosmwasm_client::types::SimulateResponse; -use validator_client::nymd::{AccountId as CosmosAccountId, Coin, Fee, SigningNymdClient}; +use validator_client::nyxd::cosmwasm_client::types::SimulateResponse; +use validator_client::nyxd::{AccountId as CosmosAccountId, Coin, Fee, SigningNyxdClient}; use validator_client::Client; // Some hardcoded metadata overrides @@ -65,7 +65,7 @@ impl WalletState { #[derive(Default)] pub struct WalletStateInner { config: config::Config, - signing_clients: HashMap>, + signing_clients: HashMap>, current_network: Network, // All the accounts the we get from decrypting the wallet. We hold on to these for being able to @@ -88,6 +88,17 @@ pub(crate) struct WalletAccountIds { } impl WalletStateInner { + pub fn attempt_convert_to_fixed_fee(&self, coin: DecCoin) -> Result { + // first we have to convert the coin to its base denomination + let base_coin = self.attempt_convert_to_base_coin(coin)?; + + // then we get the gas price for the current network + let current_client = self.current_client()?; + let gas_price = current_client.nyxd.gas_price(); + + Ok(Fee::manual_with_gas_price(base_coin, gas_price.clone())) + } + // note that `Coin` is ALWAYS the base coin pub fn attempt_convert_to_base_coin(&self, coin: DecCoin) -> Result { let registered_coins = self @@ -156,8 +167,8 @@ impl WalletStateInner { ) -> Result { // this MUST succeed as we just used it before let client = self.current_client()?; - let gas_price = client.nymd.gas_price().clone(); - let gas_adjustment = client.nymd.gas_adjustment(); + let gas_price = client.nyxd.gas_price().clone(); + let gas_adjustment = client.nyxd.gas_adjustment(); let res = SimulateResult::new(simulate_response.gas_info, gas_price, gas_adjustment); @@ -169,7 +180,7 @@ impl WalletStateInner { Ok(FeeDetails::new(amount, res.to_fee())) } - pub fn client(&self, network: Network) -> Result<&Client, BackendError> { + pub fn client(&self, network: Network) -> Result<&Client, BackendError> { self.signing_clients .get(&network) .ok_or(BackendError::ClientNotInitialized) @@ -178,20 +189,20 @@ impl WalletStateInner { pub fn client_mut( &mut self, network: Network, - ) -> Result<&mut Client, BackendError> { + ) -> Result<&mut Client, BackendError> { self.signing_clients .get_mut(&network) .ok_or(BackendError::ClientNotInitialized) } - pub fn current_client(&self) -> Result<&Client, BackendError> { + pub fn current_client(&self) -> Result<&Client, BackendError> { self.signing_clients .get(&self.current_network) .ok_or(BackendError::ClientNotInitialized) } #[allow(unused)] - pub fn current_client_mut(&mut self) -> Result<&mut Client, BackendError> { + pub fn current_client_mut(&mut self) -> Result<&mut Client, BackendError> { self.signing_clients .get_mut(&self.current_network) .ok_or(BackendError::ClientNotInitialized) @@ -211,7 +222,7 @@ impl WalletStateInner { Ok(self.config.save_to_files()?) } - pub fn add_client(&mut self, network: Network, client: Client) { + pub fn add_client(&mut self, network: Network, client: Client) { self.signing_clients.insert(network, client); } @@ -258,68 +269,63 @@ impl WalletStateInner { let validators = validators_in_config .chain(fetched_validators) .chain(default_validators) - .unique_by(|v| (v.nymd_url.clone(), v.api_url.clone())); + .unique_by(|v| (v.nyxd_url.clone(), v.api_url.clone())); // Annotate with dynamic metadata validators.map(|v| { - let metadata = self.validator_metadata.get(&v.nymd_url); + let metadata = self.validator_metadata.get(&v.nyxd_url); let name = v - .nymd_name + .nyxd_name .or_else(|| metadata.and_then(|m| m.name.clone())); config::ValidatorConfigEntry { - nymd_url: v.nymd_url, - nymd_name: name, + nyxd_url: v.nyxd_url, + nyxd_name: name, api_url: v.api_url, } }) } - pub fn get_nymd_urls_only(&self, network: Network) -> impl Iterator + '_ { + pub fn get_nyxd_urls_only(&self, network: Network) -> impl Iterator + '_ { self.get_config_validator_entries(network) - .into_iter() - .map(|v| v.nymd_url) + .map(|v| v.nyxd_url) } pub fn get_api_urls_only(&self, network: Network) -> impl Iterator + '_ { self.get_config_validator_entries(network) - .into_iter() .filter_map(|v| v.api_url) } - /// Get the list of validator nymd urls in the network config format, suitable for passing on to + /// Get the list of validator nyxd urls in the network config format, suitable for passing on to /// the UI - pub fn get_nymd_urls( + pub fn get_nyxd_urls( &self, network: Network, ) -> impl Iterator + '_ { self.get_config_validator_entries(network) - .into_iter() .map(|v| network_config::ValidatorUrl { - url: v.nymd_url.to_string(), - name: v.nymd_name, + url: v.nyxd_url.to_string(), + name: v.nyxd_name, }) } - /// Get the list of validator-api urls in the network config format, suitable for passing on to + /// Get the list of nym-api urls in the network config format, suitable for passing on to /// the UI pub fn get_api_urls( &self, network: Network, ) -> impl Iterator + '_ { - self.get_config_validator_entries(network) - .into_iter() - .filter_map(|v| { - v.api_url.map(|u| network_config::ValidatorUrl { - url: u.to_string(), - name: None, - }) + self.get_config_validator_entries(network).filter_map(|v| { + v.api_url.map(|u| network_config::ValidatorUrl { + url: u.to_string(), + name: None, }) + }) } - pub fn get_all_nymd_urls(&self) -> HashMap> { + pub fn get_all_nyxd_urls(&self) -> HashMap> { Network::iter() .flat_map(|network| { - self.get_nymd_urls_only(network) + self.get_nyxd_urls_only(network) .map(move |url| (network, url)) }) .into_group_map() @@ -334,18 +340,18 @@ impl WalletStateInner { .into_group_map() } - /// Fetch validator urls remotely. These are used to in addition to the base ones, and the user + /// Fetch nyxd urls remotely. These are used to in addition to the base ones, and the user /// configured ones. - pub async fn fetch_updated_validator_urls(&mut self) -> Result<(), BackendError> { + pub async fn fetch_updated_nyxd_urls(&mut self) -> Result<(), BackendError> { let client = reqwest::Client::builder() .timeout(Duration::from_secs(3)) .build()?; log::debug!( "Fetching validator urls from: {}", - crate::config::REMOTE_SOURCE_OF_VALIDATOR_URLS + crate::config::REMOTE_SOURCE_OF_NYXD_URLS ); let response = client - .get(crate::config::REMOTE_SOURCE_OF_VALIDATOR_URLS.to_string()) + .get(crate::config::REMOTE_SOURCE_OF_NYXD_URLS.to_string()) .send() .await?; @@ -361,13 +367,13 @@ impl WalletStateInner { log::debug!("Refreshing validator status"); // All urls for all networks - let nymd_urls = self - .get_all_nymd_urls() + let nyxd_urls = self + .get_all_nyxd_urls() .into_iter() .flat_map(|(_, urls)| urls.into_iter()); // Fetch status for all urls - let responses = fetch_status_for_urls(nymd_urls).await?; + let responses = fetch_status_for_urls(nyxd_urls).await?; // Update the stored metadata self.apply_responses(responses)?; @@ -413,26 +419,18 @@ impl WalletStateInner { } } - pub fn select_validator_nymd_url( - &mut self, - url: &str, - network: Network, - ) -> Result<(), BackendError> { - self.config.select_validator_nymd_url(url.parse()?, network); + pub fn select_nyxd_url(&mut self, url: &str, network: Network) -> Result<(), BackendError> { + self.config.select_nyxd_url(url.parse()?, network); if let Ok(client) = self.client_mut(network) { - client.change_nymd(url.parse()?)?; + client.change_nyxd(url.parse()?)?; } Ok(()) } - pub fn select_validator_api_url( - &mut self, - url: &str, - network: Network, - ) -> Result<(), BackendError> { - self.config.select_validator_api_url(url.parse()?, network); + pub fn select_nym_api_url(&mut self, url: &str, network: Network) -> Result<(), BackendError> { + self.config.select_nym_api_url(url.parse()?, network); if let Ok(client) = self.client_mut(network) { - client.change_validator_api(url.parse()?); + client.change_nym_api(url.parse()?); } Ok(()) } @@ -447,13 +445,13 @@ impl WalletStateInner { } async fn fetch_status_for_urls( - nymd_urls: impl Iterator, + nyxd_urls: impl Iterator, ) -> Result>, BackendError> { let client = reqwest::Client::builder() .timeout(Duration::from_secs(3)) .build()?; - let responses = futures::future::join_all(nymd_urls.into_iter().map(|url| { + let responses = futures::future::join_all(nyxd_urls.into_iter().map(|url| { let client = &client; let status_url = url.join("status").unwrap_or_else(|_| url.clone()); async move { @@ -480,16 +478,16 @@ macro_rules! client { } #[macro_export] -macro_rules! nymd_client { +macro_rules! nyxd_client { ($state:ident) => { - $state.read().await.current_client()?.nymd + $state.read().await.current_client()?.nyxd }; } #[macro_export] macro_rules! api_client { ($state:ident) => { - $state.read().await.current_client()?.validator_api + $state.read().await.current_client()?.nym_api }; } @@ -504,17 +502,17 @@ mod tests { state.add_validator_url( config::ValidatorConfigEntry { - nymd_url: "http://nymd_url.com".parse().unwrap(), - nymd_name: Some("NymdUrl".to_string()), - api_url: Some("http://nymd_url.com/api".parse().unwrap()), + nyxd_url: "http://nyxd_url.com".parse().unwrap(), + nyxd_name: Some("NyxdUrl".to_string()), + api_url: Some("http://nyxd_url.com/api".parse().unwrap()), }, Network::MAINNET, ); state.add_validator_url( config::ValidatorConfigEntry { - nymd_url: "http://foo.com".parse().unwrap(), - nymd_name: None, + nyxd_url: "http://foo.com".parse().unwrap(), + nyxd_name: None, api_url: None, }, Network::MAINNET, @@ -522,8 +520,8 @@ mod tests { state.add_validator_url( config::ValidatorConfigEntry { - nymd_url: "http://bar.com".parse().unwrap(), - nymd_name: None, + nyxd_url: "http://bar.com".parse().unwrap(), + nyxd_name: None, api_url: None, }, Network::MAINNET, @@ -531,10 +529,10 @@ mod tests { assert_eq!( state - .get_nymd_urls_only(Network::MAINNET) + .get_nyxd_urls_only(Network::MAINNET) .collect::>(), vec![ - "http://nymd_url.com/".parse().unwrap(), + "http://nyxd_url.com/".parse().unwrap(), "http://foo.com".parse().unwrap(), "http://bar.com".parse().unwrap(), "https://rpc.nymtech.net".parse().unwrap(), @@ -545,18 +543,18 @@ mod tests { .get_api_urls_only(Network::MAINNET) .collect::>(), vec![ - "http://nymd_url.com/api".parse().unwrap(), + "http://nyxd_url.com/api".parse().unwrap(), "https://validator.nymtech.net/api/".parse().unwrap(), ], ); assert_eq!( state - .get_all_nymd_urls() + .get_all_nyxd_urls() .get(&Network::MAINNET) .unwrap() .clone(), vec![ - "http://nymd_url.com/".parse().unwrap(), + "http://nyxd_url.com/".parse().unwrap(), "http://foo.com".parse().unwrap(), "http://bar.com".parse().unwrap(), "https://rpc.nymtech.net".parse().unwrap(), diff --git a/nym-wallet/src-tauri/src/utils.rs b/nym-wallet/src-tauri/src/utils.rs index f04d47722f..c2a22fcb2f 100644 --- a/nym-wallet/src-tauri/src/utils.rs +++ b/nym-wallet/src-tauri/src/utils.rs @@ -2,16 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::BackendError; -use crate::nymd_client; +use crate::nyxd_client; use crate::state::WalletState; +use bip39::Mnemonic; use cosmwasm_std::Decimal; -use mixnet_contract_common::{IdentityKey, MixId, Percent}; +use nym_mixnet_contract_common::{IdentityKey, MixId, Percent}; use nym_types::currency::DecCoin; use nym_types::mixnode::MixNodeCostParams; use nym_wallet_types::app::AppEnv; -use serde::{Deserialize, Serialize}; -use validator_client::nymd::traits::MixnetQueryClient; -use validator_client::nymd::{tx, Coin, CosmosCoin, Gas, GasPrice}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use validator_client::nyxd::traits::MixnetQueryClient; +use validator_client::nyxd::{tx, Coin, CosmosCoin, Gas, GasPrice}; +use zeroize::Zeroize; fn get_env_as_option(key: &str) -> Option { match ::std::env::var(key) { @@ -31,8 +33,8 @@ pub fn get_env() -> AppEnv { #[tauri::command] pub async fn owns_mixnode(state: tauri::State<'_, WalletState>) -> Result { - Ok(nymd_client!(state) - .get_owned_mixnode(nymd_client!(state).address()) + Ok(nyxd_client!(state) + .get_owned_mixnode(nyxd_client!(state).address()) .await? .mixnode_details .is_some()) @@ -40,8 +42,8 @@ pub async fn owns_mixnode(state: tauri::State<'_, WalletState>) -> Result) -> Result { - Ok(nymd_client!(state) - .get_owned_gateway(nymd_client!(state).address()) + Ok(nyxd_client!(state) + .get_owned_gateway(nyxd_client!(state).address()) .await? .gateway .is_some()) @@ -52,7 +54,7 @@ pub async fn try_convert_pubkey_to_mix_id( state: tauri::State<'_, WalletState>, mix_identity: IdentityKey, ) -> Result, BackendError> { - let res = nymd_client!(state) + let res = nyxd_client!(state) .get_mixnode_details_by_identity(mix_identity) .await?; Ok(res.map(|mixnode_details| mixnode_details.mix_id())) @@ -185,10 +187,101 @@ pub async fn get_old_and_incorrect_hardcoded_fee( operation: Operation, ) -> Result { let guard = state.read().await; - let mut approximate_fee = operation.default_fee(guard.current_client()?.nymd.gas_price()); + let mut approximate_fee = operation.default_fee(guard.current_client()?.nyxd.gas_price()); // on all our chains it should only ever contain a single type of currency assert_eq!(approximate_fee.amount.len(), 1); let coin: Coin = approximate_fee.amount.pop().unwrap().into(); log::info!("hardcoded fee for {:?} is {:?}", operation, coin); guard.attempt_convert_to_display_dec_coin(coin) } + +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct SensitiveStringWrapper(String); + +impl<'de> Deserialize<'de> for SensitiveStringWrapper { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(SensitiveStringWrapper(String::deserialize(deserializer)?)) + } +} + +impl Serialize for SensitiveStringWrapper { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // unfortunately this serialized value will live on... + self.0.serialize(serializer) + } +} + +impl From for SensitiveStringWrapper { + fn from(value: String) -> Self { + SensitiveStringWrapper(value) + } +} + +impl AsRef for SensitiveStringWrapper { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +#[derive(Clone)] +// can't do it natively until https://github.com/rust-bitcoin/rust-bip39/pull/32 gets merged and released... +pub(crate) struct ZeroizeMnemonicWrapper(bip39::Mnemonic); + +impl From for ZeroizeMnemonicWrapper { + fn from(value: Mnemonic) -> Self { + ZeroizeMnemonicWrapper(value) + } +} + +impl Zeroize for ZeroizeMnemonicWrapper { + fn zeroize(&mut self) { + // overwrite the mnemonic value with a completely random one + // (a poor man's zeroize until bip39 crate does it properly...) + self.0 = Mnemonic::generate(self.0.word_count()).unwrap(); + } +} + +impl Drop for ZeroizeMnemonicWrapper { + fn drop(&mut self) { + self.zeroize() + } +} + +impl AsRef for ZeroizeMnemonicWrapper { + fn as_ref(&self) -> &Mnemonic { + &self.0 + } +} + +impl ZeroizeMnemonicWrapper { + pub(crate) fn into_string(self) -> SensitiveStringWrapper { + SensitiveStringWrapper(self.0.to_string()) + } + + pub(crate) fn try_from_string(string: SensitiveStringWrapper) -> Result { + let res = string.as_ref().parse()?; + Ok(ZeroizeMnemonicWrapper(res)) + } + + // special care must be taken when calling this method as the mnemonic will no longer get zeroized! + pub(crate) fn into_cloned_inner(self) -> bip39::Mnemonic { + self.0.clone() + } + + #[cfg(test)] + pub(crate) fn unchecked_clone_inner(&self) -> bip39::Mnemonic { + self.0.clone() + } + + #[cfg(test)] + pub(crate) fn generate_random() -> Self { + ZeroizeMnemonicWrapper(Mnemonic::generate(24).unwrap()) + } +} diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index d0027592d9..9451c065b7 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -15,10 +15,11 @@ // instead treating as muliple accounts with one entry. use serde::{Deserialize, Serialize}; -use validator_client::nymd::bip32::DerivationPath; +use validator_client::nyxd::bip32::DerivationPath; use zeroize::Zeroize; use crate::error::BackendError; +use crate::utils::ZeroizeMnemonicWrapper; use super::encryption::EncryptedData; use super::password::{AccountId, LoginId}; @@ -193,6 +194,7 @@ impl StoredLogin { /// Multiple stored accounts, each entry having an id and a data field. #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] +#[zeroize(drop)] pub(crate) struct MultipleAccounts { accounts: Vec, } @@ -220,8 +222,8 @@ impl MultipleAccounts { .find(|account| account.mnemonic() == mnemonic) } - pub(crate) fn into_accounts(self) -> impl Iterator { - self.accounts.into_iter() + pub(crate) fn inner(&self) -> &[WalletAccount] { + &self.accounts } #[allow(unused)] @@ -236,17 +238,19 @@ impl MultipleAccounts { pub(crate) fn add( &mut self, id: AccountId, - mnemonic: bip39::Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, hd_path: DerivationPath, ) -> Result<(), BackendError> { if self.get_account(&id).is_some() { Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin) - } else if self.get_account_with_mnemonic(&mnemonic).is_some() { + } else if self.get_account_with_mnemonic(mnemonic.as_ref()).is_some() { Err(BackendError::WalletMnemonicAlreadyExistsInWalletLogin) } else { self.accounts.push(WalletAccount::new( id, - MnemonicAccount::new(mnemonic, hd_path), + // safety: the call to `clone_inner` is fine here as the raw mnemonic will get immediately + // passed to `MnemonicAccount` which will zeroize it on drop + MnemonicAccount::new(mnemonic.into_cloned_inner(), hd_path), )); Ok(()) } @@ -269,6 +273,7 @@ impl From> for MultipleAccounts { /// An entry in the list of stored accounts #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] +#[zeroize(drop)] pub(crate) struct WalletAccount { id: AccountId, account: AccountData, @@ -356,7 +361,7 @@ impl Drop for MnemonicAccount { mod display_hd_path { use serde::{Deserialize, Deserializer, Serializer}; - use validator_client::nymd::bip32::DerivationPath; + use validator_client::nyxd::bip32::DerivationPath; pub fn serialize( hd_path: &DerivationPath, diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index bf9e3c3c6c..57464eda49 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -14,11 +14,12 @@ pub(crate) use crate::wallet_storage::password::{AccountId, LoginId, UserPasswor use crate::error::BackendError; use crate::platform_constants::{STORAGE_DIR_NAME, WALLET_INFO_FILENAME}; -use chrono::Local; +use crate::utils::ZeroizeMnemonicWrapper; use std::ffi::OsString; use std::fs::{self, create_dir_all, OpenOptions}; use std::path::{Path, PathBuf}; -use validator_client::nymd::bip32::DerivationPath; +use time::OffsetDateTime; +use validator_client::nyxd::bip32::DerivationPath; #[cfg(test)] use self::account_data::MnemonicAccount; @@ -138,7 +139,7 @@ fn store_login_at_file( } pub(crate) fn store_login_with_multiple_accounts( - mnemonic: bip39::Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, hd_path: DerivationPath, id: LoginId, password: &UserPassword, @@ -153,7 +154,7 @@ pub(crate) fn store_login_with_multiple_accounts( fn store_login_with_multiple_accounts_at_file( filepath: &Path, - mnemonic: bip39::Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, hd_path: DerivationPath, id: LoginId, password: &UserPassword, @@ -184,7 +185,7 @@ fn store_login_with_multiple_accounts_at_file( /// account in the list of accounts associated with the encrypted entry. The inner id for this /// entry will be set to the same as the outer, unencrypted, id. pub(crate) fn append_account_to_login( - mnemonic: bip39::Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, hd_path: DerivationPath, id: LoginId, inner_id: AccountId, @@ -200,7 +201,7 @@ pub(crate) fn append_account_to_login( fn append_account_to_login_at_file( filepath: &Path, - mnemonic: bip39::Mnemonic, + mnemonic: ZeroizeMnemonicWrapper, hd_path: DerivationPath, id: LoginId, inner_id: AccountId, @@ -283,7 +284,10 @@ fn append_timestamp_to_filename( } fn _archive_wallet_file(path: &Path) -> Result<(), BackendError> { - let timestamp: OsString = Local::now().timestamp_millis().to_string().into(); + let timestamp: OsString = OffsetDateTime::now_utc() + .unix_timestamp() + .to_string() + .into(); let mut additional_number = 0; let mut new_path = append_timestamp_to_filename(path, timestamp.clone(), None)?; @@ -392,7 +396,7 @@ mod tests { use crate::wallet_storage::account_data::{MnemonicAccount, WalletAccount}; use super::*; - use config::defaults::COSMOS_DERIVATION_PATH; + use nym_config::defaults::COSMOS_DERIVATION_PATH; use std::str::FromStr; use tempfile::tempdir; @@ -418,12 +422,19 @@ mod tests { fn store_single_login() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); - store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap(); + store_login_at_file( + &wallet_file, + account1.into_cloned_inner(), + hd_path, + id1.clone(), + &password, + ) + .unwrap(); let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); assert_eq!(stored_wallet.len(), 1); @@ -439,7 +450,7 @@ mod tests { fn store_single_login_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -467,7 +478,7 @@ mod tests { fn store_twice_for_the_same_id_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -475,7 +486,7 @@ mod tests { // Store the first login store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -484,7 +495,13 @@ mod tests { // and storing the same id again fails assert!(matches!( - store_login_at_file(&wallet_file, account1, hd_path, id1, &password,), + store_login_at_file( + &wallet_file, + account1.into_cloned_inner(), + hd_path, + id1, + &password, + ), Err(BackendError::WalletLoginIdAlreadyExists), )); } @@ -493,7 +510,7 @@ mod tests { fn store_twice_for_the_same_id_fails_with_multiple() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -525,13 +542,20 @@ mod tests { fn load_with_wrong_password_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); let id1 = LoginId::new("first".to_string()); - store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap(); + store_login_at_file( + &wallet_file, + account1.into_cloned_inner(), + hd_path, + id1.clone(), + &password, + ) + .unwrap(); // Trying to load it with wrong password now fails assert!(matches!( @@ -544,7 +568,7 @@ mod tests { fn load_with_wrong_password_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); @@ -570,13 +594,20 @@ mod tests { fn load_with_wrong_id_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap(); + store_login_at_file( + &wallet_file, + account1.into_cloned_inner(), + hd_path, + id1, + &password, + ) + .unwrap(); // Trying to load with the wrong id assert!(matches!( @@ -589,7 +620,7 @@ mod tests { fn load_with_wrong_id_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -609,14 +640,14 @@ mod tests { fn store_and_load_a_single_login() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -625,7 +656,7 @@ mod tests { let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc.mnemonic()); + assert_eq!(account1.as_ref(), acc.mnemonic()); assert_eq!(&hd_path, acc.hd_path()); } @@ -633,7 +664,7 @@ mod tests { fn store_and_load_a_single_login_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let acc1 = bip39::Mnemonic::generate(24).unwrap(); + let acc1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -654,7 +685,7 @@ mod tests { .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) .unwrap(); assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); - assert_eq!(account.mnemonic(), &acc1); + assert_eq!(account.mnemonic(), acc1.as_ref()); assert_eq!(account.hd_path(), &hd_path); } @@ -662,8 +693,8 @@ mod tests { fn store_a_second_login_with_a_different_password_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); @@ -672,7 +703,7 @@ mod tests { store_login_at_file( &wallet_file, - account1, + account1.into_cloned_inner(), cosmos_hd_path.clone(), id1, &password, @@ -681,7 +712,13 @@ mod tests { // Can't store a second login if you use different password assert!(matches!( - store_login_at_file(&wallet_file, account2, cosmos_hd_path, id2, &bad_password), + store_login_at_file( + &wallet_file, + account2.into_cloned_inner(), + cosmos_hd_path, + id2, + &bad_password + ), Err(BackendError::WalletDifferentPasswordDetected), )); } @@ -690,8 +727,8 @@ mod tests { fn store_a_second_login_with_a_different_password_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); @@ -724,8 +761,8 @@ mod tests { fn store_two_mnemonic_accounts_gives_different_salts_and_iv() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -733,7 +770,14 @@ mod tests { let id2 = LoginId::new("second".to_string()); // Store the first account - store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap(); + store_login_at_file( + &wallet_file, + account1.into_cloned_inner(), + hd_path, + id1, + &password, + ) + .unwrap(); let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); let encrypted_blob = &stored_wallet @@ -746,7 +790,14 @@ mod tests { let original_salt = encrypted_blob.salt().to_vec(); // Add an extra account - store_login_at_file(&wallet_file, account2, different_hd_path, id2, &password).unwrap(); + store_login_at_file( + &wallet_file, + account2.into_cloned_inner(), + different_hd_path, + id2, + &password, + ) + .unwrap(); let loaded_accounts = load_existing_wallet_at_file(&wallet_file).unwrap(); assert_eq!(loaded_accounts.len(), 2); @@ -764,8 +815,8 @@ mod tests { fn store_two_mnemonic_accounts_using_two_logins() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -775,7 +826,7 @@ mod tests { // Store the first account store_login_at_file( &wallet, - account1.clone(), + account1.unchecked_clone_inner(), cosmos_hd_path.clone(), id1.clone(), &password, @@ -784,13 +835,13 @@ mod tests { let login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); let acc = login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc.mnemonic()); + assert_eq!(account1.as_ref(), acc.mnemonic()); assert_eq!(&cosmos_hd_path, acc.hd_path()); // Add an extra account store_login_at_file( &wallet, - account2.clone(), + account2.unchecked_clone_inner(), different_hd_path.clone(), id2.clone(), &password, @@ -800,12 +851,12 @@ mod tests { // first account should be unchanged let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(account1.as_ref(), acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); let loaded_login = load_existing_login_at_file(&wallet, &id2, &password).unwrap(); let acc2 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account2, acc2.mnemonic()); + assert_eq!(account2.as_ref(), acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); } @@ -813,8 +864,8 @@ mod tests { fn store_one_mnemonic_account_and_one_multi_account() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -824,7 +875,7 @@ mod tests { // Store the first account store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -833,7 +884,7 @@ mod tests { let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc.mnemonic()); + assert_eq!(account1.as_ref(), acc.mnemonic()); assert_eq!(&hd_path, acc.hd_path()); // Add an extra account @@ -849,7 +900,7 @@ mod tests { // first account should be unchanged let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(account1.as_ref(), acc1.mnemonic()); assert_eq!(&hd_path, acc1.hd_path()); let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); @@ -859,7 +910,7 @@ mod tests { .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) .unwrap(); assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); - assert_eq!(account.mnemonic(), &account2); + assert_eq!(account.mnemonic(), account2.as_ref()); assert_eq!(account.hd_path(), &different_hd_path); } @@ -867,7 +918,7 @@ mod tests { fn remove_non_existent_id_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -887,8 +938,8 @@ mod tests { fn store_and_remove_wallet_login_information() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -898,7 +949,7 @@ mod tests { // Store two accounts with two different passwords store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), cosmos_hd_path.clone(), id1.clone(), &password, @@ -906,7 +957,7 @@ mod tests { .unwrap(); store_login_at_file( &wallet_file, - account2.clone(), + account2.unchecked_clone_inner(), different_hd_path.clone(), id2.clone(), &password, @@ -916,12 +967,12 @@ mod tests { // Load and compare let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(account1.as_ref(), acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let acc2 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account2, acc2.mnemonic()); + assert_eq!(account2.as_ref(), acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); // Delete the second account @@ -930,7 +981,7 @@ mod tests { // The first account should be unchanged let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(account1.as_ref(), acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); // And we can't load the second one anymore @@ -957,8 +1008,8 @@ mod tests { fn append_account_converts_the_type() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -966,7 +1017,7 @@ mod tests { store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -976,7 +1027,7 @@ mod tests { // Check that it's there as the correct non-multiple type let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(acc.mnemonic(), &account1); + assert_eq!(acc.mnemonic(), account1.as_ref()); assert_eq!(acc.hd_path(), &hd_path); append_account_to_login_at_file( @@ -993,8 +1044,14 @@ mod tests { let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new(id1.into(), MnemonicAccount::new(account1, hd_path.clone())), - WalletAccount::new(id2, MnemonicAccount::new(account2, hd_path)), + WalletAccount::new( + id1.into(), + MnemonicAccount::new(account1.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id2, + MnemonicAccount::new(account2.into_cloned_inner(), hd_path), + ), ] .into(); assert_eq!(loaded_accounts, &expected); @@ -1004,10 +1061,10 @@ mod tests { fn append_accounts_to_existing_login() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); - let account4 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); + let account3 = ZeroizeMnemonicWrapper::generate_random(); + let account4 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1017,7 +1074,7 @@ mod tests { store_login_at_file( &wallet_file, - account1.clone(), + account1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -1026,7 +1083,7 @@ mod tests { store_login_at_file( &wallet_file, - account2.clone(), + account2.unchecked_clone_inner(), hd_path.clone(), id2.clone(), &password, @@ -1036,7 +1093,7 @@ mod tests { // Check that it's there as the correct non-multiple type let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let acc2 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(acc2.mnemonic(), &account2); + assert_eq!(acc2.mnemonic(), account2.as_ref()); assert_eq!(acc2.hd_path(), &hd_path); // Add a third and fourth mnenonic grouped together with the second one @@ -1062,15 +1119,24 @@ mod tests { // Check that we can load all four let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(acc1.mnemonic(), &account1); + assert_eq!(acc1.mnemonic(), account1.as_ref()); assert_eq!(acc1.hd_path(), &hd_path); let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), - WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())), - WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)), + WalletAccount::new( + id2.into(), + MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id3, + MnemonicAccount::new(account3.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id4, + MnemonicAccount::new(account4.into_cloned_inner(), hd_path), + ), ] .into(); assert_eq!(loaded_accounts, &expected); @@ -1080,10 +1146,10 @@ mod tests { fn append_accounts_to_existing_login_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); - let account4 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); + let account3 = ZeroizeMnemonicWrapper::generate_random(); + let account4 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1134,7 +1200,7 @@ mod tests { let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(account1, hd_path.clone()), + MnemonicAccount::new(account1.into_cloned_inner(), hd_path.clone()), )] .into(); assert_eq!(loaded_accounts, &expected); @@ -1144,10 +1210,16 @@ mod tests { let expected = vec![ WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(account2, hd_path.clone()), + MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id3, + MnemonicAccount::new(account3.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id4, + MnemonicAccount::new(account4.into_cloned_inner(), hd_path), ), - WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())), - WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)), ] .into(); assert_eq!(loaded_accounts, &expected); @@ -1157,7 +1229,7 @@ mod tests { fn append_the_same_mnemonic_twice_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1182,14 +1254,21 @@ mod tests { fn delete_the_same_account_twice_for_a_login_fails() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_login_at_file(&wallet, account1, hd_path.clone(), id1.clone(), &password).unwrap(); + store_login_at_file( + &wallet, + account1.into_cloned_inner(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); append_account_to_login_at_file( &wallet, @@ -1213,8 +1292,8 @@ mod tests { fn delete_the_same_account_twice_for_a_login_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1251,9 +1330,9 @@ mod tests { fn delete_appended_account_doesnt_affect_others() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); + let account3 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1262,7 +1341,7 @@ mod tests { store_login_at_file( &wallet_file, - account1, + account1.into_cloned_inner(), hd_path.clone(), id1.clone(), &password, @@ -1271,7 +1350,7 @@ mod tests { store_login_at_file( &wallet_file, - account2.clone(), + account2.unchecked_clone_inner(), hd_path.clone(), id2.clone(), &password, @@ -1294,8 +1373,14 @@ mod tests { let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), - WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path)), + WalletAccount::new( + id2.into(), + MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id3, + MnemonicAccount::new(account3.into_cloned_inner(), hd_path), + ), ] .into(); assert_eq!(loaded_accounts, &expected); @@ -1305,8 +1390,8 @@ mod tests { fn remove_all_accounts_for_a_login_removes_the_file_when_empty() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1354,9 +1439,9 @@ mod tests { fn remove_all_accounts_for_a_login_removes_that_login() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); - let account2 = bip39::Mnemonic::generate(24).unwrap(); - let account3 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = ZeroizeMnemonicWrapper::generate_random(); + let account2 = ZeroizeMnemonicWrapper::generate_random(); + let account3 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1411,7 +1496,7 @@ mod tests { let acc3 = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(account3, hd_path), + MnemonicAccount::new(account3.into_cloned_inner(), hd_path), )] .into(); assert_eq!(acc3, &expected); @@ -1421,10 +1506,10 @@ mod tests { fn append_accounts_and_remove_appended_accounts() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let acc1 = bip39::Mnemonic::generate(24).unwrap(); - let acc2 = bip39::Mnemonic::generate(24).unwrap(); - let acc3 = bip39::Mnemonic::generate(24).unwrap(); - let acc4 = bip39::Mnemonic::generate(24).unwrap(); + let acc1 = ZeroizeMnemonicWrapper::generate_random(); + let acc2 = ZeroizeMnemonicWrapper::generate_random(); + let acc3 = ZeroizeMnemonicWrapper::generate_random(); + let acc4 = ZeroizeMnemonicWrapper::generate_random(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1434,7 +1519,7 @@ mod tests { store_login_at_file( &wallet, - acc1.clone(), + acc1.unchecked_clone_inner(), hd_path.clone(), id1.clone(), &password, @@ -1443,7 +1528,7 @@ mod tests { store_login_at_file( &wallet, - acc2.clone(), + acc2.unchecked_clone_inner(), hd_path.clone(), id2.clone(), &password, @@ -1479,9 +1564,12 @@ mod tests { let expected = vec![ WalletAccount::new( id2.clone().into(), - MnemonicAccount::new(acc2, hd_path.clone()), + MnemonicAccount::new(acc2.into_cloned_inner(), hd_path.clone()), + ), + WalletAccount::new( + id4.clone(), + MnemonicAccount::new(acc4.into_cloned_inner(), hd_path.clone()), ), - WalletAccount::new(id4.clone(), MnemonicAccount::new(acc4, hd_path.clone())), ] .into(); assert_eq!(loaded_accounts, &expected); @@ -1497,7 +1585,7 @@ mod tests { // The first login is still available let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); let account = loaded_login.as_mnemonic_account().unwrap(); - assert_eq!(account.mnemonic(), &acc1); + assert_eq!(account.mnemonic(), acc1.as_ref()); assert_eq!(account.hd_path(), &hd_path); } diff --git a/nym-wallet/src-tauri/src/wallet_storage/password.rs b/nym-wallet/src-tauri/src/wallet_storage/password.rs index dfeacf33a8..d523c87496 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/password.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/password.rs @@ -3,7 +3,7 @@ use std::fmt; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use zeroize::Zeroize; // The `LoginId` is the top level id in the wallet file, and is not stored encrypted @@ -83,11 +83,12 @@ impl fmt::Display for AccountId { // simple wrapper for String that will get zeroized on drop #[derive(Zeroize)] #[zeroize(drop)] -pub(crate) struct UserPassword(String); +pub struct UserPassword(String); impl UserPassword { - pub(crate) fn new(pass: String) -> UserPassword { - UserPassword(pass) + #[cfg(test)] + pub(crate) fn new(inner: String) -> Self { + UserPassword(inner) } pub(crate) fn as_bytes(&self) -> &[u8] { @@ -95,8 +96,11 @@ impl UserPassword { } } -impl AsRef for UserPassword { - fn as_ref(&self) -> &str { - self.0.as_ref() +impl<'de> Deserialize<'de> for UserPassword { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(UserPassword(String::deserialize(deserializer)?)) } } diff --git a/nym-wallet/src-tauri/tauri.conf.json b/nym-wallet/src-tauri/tauri.conf.json index e6c2dad6b9..c6374a010f 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.1.4" + "version": "1.1.12" }, "build": { "distDir": "../dist", @@ -14,7 +14,13 @@ "active": true, "targets": "all", "identifier": "net.nymtech.wallet", - "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], "resources": [], "externalBin": [], "copyright": "Copyright © 2021-2022 Nym Technologies SA", @@ -27,7 +33,6 @@ "macOS": { "frameworks": [], "minimumSystemVersion": "", - "exceptionDomain": "", "signingIdentity": "Developer ID Application: Nym Technologies SA (VW5DZLFHM5)", "entitlements": null @@ -40,7 +45,9 @@ }, "updater": { "active": true, - "endpoints": ["https://nymtech.net/.wellknown/wallet/updater.json"], + "endpoints": [ + "https://nymtech.net/.wellknown/wallet/updater.json" + ], "dialog": true, "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENCNzQ2M0E5N0VFODE2NApSV1JrZ2U2WE9rYTNETTg1OTBKdE5uWUEra0hML2syOVUvQ2lxZmFZRzZ1T3NWbGM0eVRzUTVhVwo=" }, @@ -67,4 +74,4 @@ "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" } } -} +} \ No newline at end of file diff --git a/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx b/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx index c23f392bbe..62eb3a4643 100644 --- a/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx +++ b/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx @@ -5,11 +5,12 @@ import { SimpleModal } from '../Modals/SimpleModal'; import { Warning } from '../Warning'; const passwordCreationSteps = [ - 'Log out of your wallet', + 'Log out from the wallet', 'Sign in using “Sign in with mnemonic” button', - 'On the next screen select “Create a password for your account”', - 'Sign in to the wallet with your new password', - 'Then come back here to import or create new accounts', + 'On the next screen select “Create a password"', + 'Type in the mnemonic you want to create a password for and follow the next steps', + 'Sign back in the wallet using your new password', + 'Come back to this page to import or create new accounts', ]; // TODO add the link href value diff --git a/nym-wallet/src/components/Accounts/modals/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/modals/AddAccountModal.tsx index 61d6f7b9e5..ac2132e636 100644 --- a/nym-wallet/src/components/Accounts/modals/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/modals/AddAccountModal.tsx @@ -10,7 +10,6 @@ import { TextField, Typography, } from '@mui/material'; -import { useClipboard } from 'use-clipboard-copy'; import { createMnemonic, validateMnemonic } from 'src/requests'; import { Console } from 'src/utils/console'; import { AccountsContext } from 'src/context'; @@ -30,16 +29,16 @@ const importAccountSteps = [ ]; const MnemonicStep = ({ mnemonic, onNext, onBack }: { mnemonic: string; onNext: () => void; onBack: () => void }) => { - const { copy, copied } = useClipboard({ copiedTimeout: 5000 }); + const [confirmed, setConfirmed] = useState(false); return ( - + - diff --git a/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx b/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx index bede36211b..e2d6e02c2a 100644 --- a/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx +++ b/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx @@ -3,25 +3,20 @@ import { Box, Button, CircularProgress, - Paper, Dialog, DialogActions, DialogContent, DialogTitle, - IconButton, + Paper, Typography, } from '@mui/material'; -import { ArrowBackSharp } from '@mui/icons-material'; import { AccountsContext } from 'src/context'; -import { useClipboard } from 'use-clipboard-copy'; -import { PasswordInput, Mnemonic } from 'src/components'; +import { Mnemonic, PasswordInput } from 'src/components'; import { StyledBackButton } from 'src/components/StyledBackButton'; export const MnemonicModal = () => { const [password, setPassword] = useState(''); - const { copy, copied } = useClipboard({ copiedTimeout: 5000 }); - const { dialogToDisplay, setDialogToDisplay, @@ -74,7 +69,7 @@ export const MnemonicModal = () => { /> ) : ( - + )} diff --git a/nym-wallet/src/components/ActionsMenu.tsx b/nym-wallet/src/components/ActionsMenu.tsx index 227af88407..f9a6ef035d 100644 --- a/nym-wallet/src/components/ActionsMenu.tsx +++ b/nym-wallet/src/components/ActionsMenu.tsx @@ -2,12 +2,12 @@ import React, { useRef } from 'react'; import { MoreVertSharp } from '@mui/icons-material'; import { IconButton, ListItemIcon, ListItemText, Menu, MenuItem } from '@mui/material'; -export const ActionsMenu: React.FC<{ open: boolean; onOpen: () => void; onClose: () => void }> = ({ - children, - open, - onOpen, - onClose, -}) => { +export const ActionsMenu: FCWithChildren<{ + open: boolean; + children: React.ReactNode; + onOpen: () => void; + onClose: () => void; +}> = ({ children, open, onOpen, onClose }) => { const anchorEl: any = useRef(); return ( diff --git a/nym-wallet/src/components/AppBar.tsx b/nym-wallet/src/components/AppBar.tsx index 24b2f93942..3022646798 100644 --- a/nym-wallet/src/components/AppBar.tsx +++ b/nym-wallet/src/components/AppBar.tsx @@ -15,7 +15,7 @@ export const AppBar = () => { const navigate = useNavigate(); return ( - + diff --git a/nym-wallet/src/components/Balance/VestingTimeline.tsx b/nym-wallet/src/components/Balance/VestingTimeline.tsx new file mode 100644 index 0000000000..cfb489056d --- /dev/null +++ b/nym-wallet/src/components/Balance/VestingTimeline.tsx @@ -0,0 +1,65 @@ +/* eslint-disable react/no-array-index-key */ +import React, { useContext } from 'react'; +import { useTheme } from '@mui/material/styles'; +import { Box, Stack, Tooltip, Typography } from '@mui/material'; +import { format } from 'date-fns'; +import { AppContext } from 'src/context'; + +const calculateMarkerPosition = (arrLength: number, index: number) => (1 / arrLength) * 100 * index; + +const Marker: FCWithChildren<{ tooltipText: string; color: string; position: string }> = ({ + tooltipText, + color, + position, +}) => ( + + + +); + +export const VestingTimeline: FCWithChildren<{ percentageComplete: number }> = ({ percentageComplete }) => { + const { + userBalance: { currentVestingPeriod, vestingAccountInfo }, + } = useContext(AppContext); + + const theme = useTheme(); + + const nextPeriod = + typeof currentVestingPeriod === 'object' && !!vestingAccountInfo?.periods + ? Number(vestingAccountInfo?.periods[currentVestingPeriod.In + 1]?.start_time) + : undefined; + + return ( + + + {percentageComplete}% + + + + {vestingAccountInfo?.periods.map((period, i, arr) => ( + = calculateMarkerPosition(arr.length, i) + ? theme.palette.success.main + : '#B9B9B9' + } + tooltipText={format(new Date(Number(period.start_time) * 1000), 'HH:mm do MMM yyyy')} + key={i} + /> + ))} + + + + {!!nextPeriod && ( + + Next vesting period: {format(new Date(nextPeriod * 1000), 'HH:mm do MMM yyyy')} + + )} + + ); +}; diff --git a/nym-wallet/src/components/Balance/cards/TokenTransfer.tsx b/nym-wallet/src/components/Balance/cards/TokenTransfer.tsx new file mode 100644 index 0000000000..1600a9b54e --- /dev/null +++ b/nym-wallet/src/components/Balance/cards/TokenTransfer.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Card, Stack, Button } from '@mui/material'; +import { ModalListItem } from 'src/components/Modals/ModalListItem'; + +export const TokenTransfer = ({ + onTransfer, + unlockedTokens, + unlockedRewards, + unlockedTransferable, +}: { + unlockedTokens?: string; + unlockedRewards?: string; + unlockedTransferable?: string; + onTransfer: () => void; +}) => ( + + + + + + + + + + +); diff --git a/nym-wallet/src/components/Balance/cards/VestingSchedule.tsx b/nym-wallet/src/components/Balance/cards/VestingSchedule.tsx new file mode 100644 index 0000000000..b26b999970 --- /dev/null +++ b/nym-wallet/src/components/Balance/cards/VestingSchedule.tsx @@ -0,0 +1,109 @@ +import React, { useContext, useState, useEffect } from 'react'; +import { + TableContainer, + Table, + TableHead, + TableRow, + TableCell, + TableBody, + Typography, + TableCellProps, + Card, +} from '@mui/material'; +import { Period } from '@nymproject/types'; +import { AppContext } from 'src/context'; +import { VestingTimeline } from '../VestingTimeline'; + +const columnsHeaders: Array<{ title: string; align: TableCellProps['align'] }> = [ + { title: 'Locked', align: 'left' }, + { title: 'Period', align: 'left' }, + { title: 'Unlocked', align: 'right' }, +]; + +const vestingPeriod = (current?: Period, original?: number) => { + if (current === 'After') return 'Complete'; + + if (typeof current === 'object' && typeof original === 'number') return `${current.In + 1}/${original}`; + + return 'N/A'; +}; + +export const VestingSchedule = () => { + const { userBalance, clientDetails } = useContext(AppContext); + const [vestedPercentage, setVestedPercentage] = useState(0); + + const calculatePercentage = () => { + const { tokenAllocation, originalVesting } = userBalance; + if (tokenAllocation?.vesting && tokenAllocation.vested && tokenAllocation.vested !== '0' && originalVesting) { + const percentage = (+tokenAllocation.vested / +originalVesting.amount.amount) * 100; + const rounded = percentage.toFixed(2); + setVestedPercentage(+rounded); + } else { + setVestedPercentage(0); + } + }; + + useEffect(() => { + calculatePercentage(); + }, [userBalance.tokenAllocation, calculatePercentage]); + + return ( + + +

+ + + {columnsHeaders.map((header) => ( + + {header.title} + + ))} + + + + + + {userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} + {clientDetails?.display_mix_denom.toUpperCase()} + + + {vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)} + + + {userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} + {clientDetails?.display_mix_denom.toUpperCase()} + + + +
+
+ + Percentage + + + + ); +}; diff --git a/nym-wallet/src/pages/balance/components/TransferModal.tsx b/nym-wallet/src/components/Balance/modals/TransferModal.tsx similarity index 77% rename from nym-wallet/src/pages/balance/components/TransferModal.tsx rename to nym-wallet/src/components/Balance/modals/TransferModal.tsx index 02a8318064..1d3713b184 100644 --- a/nym-wallet/src/pages/balance/components/TransferModal.tsx +++ b/nym-wallet/src/components/Balance/modals/TransferModal.tsx @@ -1,6 +1,5 @@ import React, { useContext, useEffect, useState } from 'react'; import { Alert, Box, CircularProgress } from '@mui/material'; -import { CurrencyDenom, FeeDetails } from '@nymproject/types'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { ModalListItem } from 'src/components/Modals/ModalListItem'; import { AppContext, urls } from 'src/context'; @@ -8,36 +7,22 @@ import { FeeWarning } from 'src/components/FeeWarning'; import { withdrawVestedCoins } from 'src/requests'; import { Console } from 'src/utils/console'; import { simulateWithdrawVestedCoins } from 'src/requests/simulate'; -import { SuccessModal } from './TransferModalSuccess'; -import { TResponseState, TTransactionDetails } from '../types'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { SuccessModal, TTransactionDetails } from './TransferModalSuccess'; +import { TResponseState } from '../../../pages/balance/types'; export const TransferModal = ({ onClose }: { onClose: () => void }) => { const [state, setState] = useState(); - const [fee, setFee] = useState(); + const [tx, setTx] = useState(); const { userBalance, clientDetails, network } = useContext(AppContext); - - const getFee = async () => { - if (userBalance.tokenAllocation?.spendable && clientDetails?.display_mix_denom) { - try { - const simulatedFee = await simulateWithdrawVestedCoins({ - amount: { amount: userBalance.tokenAllocation?.spendable, denom: clientDetails?.display_mix_denom }, - }); - setFee(simulatedFee); - await userBalance.refreshBalances(); - } catch (e) { - setFee({ - amount: { amount: 'n/a', denom: clientDetails?.display_mix_denom.toUpperCase() as CurrencyDenom }, - fee: { Auto: null }, - }); - Console.error(e); - } - } - }; + const { fee, getFee } = useGetFee(); useEffect(() => { - getFee(); + getFee(simulateWithdrawVestedCoins, { + amount: { amount: userBalance.tokenAllocation?.spendable, denom: clientDetails?.display_mix_denom }, + }); }, []); const handleTransfer = async () => { diff --git a/nym-wallet/src/pages/balance/components/TransferModalSuccess.tsx b/nym-wallet/src/components/Balance/modals/TransferModalSuccess.tsx similarity index 92% rename from nym-wallet/src/pages/balance/components/TransferModalSuccess.tsx rename to nym-wallet/src/components/Balance/modals/TransferModalSuccess.tsx index b27c679d5a..13b762bcb5 100644 --- a/nym-wallet/src/pages/balance/components/TransferModalSuccess.tsx +++ b/nym-wallet/src/components/Balance/modals/TransferModalSuccess.tsx @@ -2,7 +2,8 @@ import React from 'react'; import { Stack, Typography } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; import { ConfirmationModal } from 'src/components'; -import { TTransactionDetails } from '../types'; + +export type TTransactionDetails = { amount: string; url: string }; export const SuccessModal = ({ tx, onClose }: { tx?: TTransactionDetails; onClose: () => void }) => ( void; }) => { + const [nextEpoch, setNextEpoch] = useState(); const navigate = useNavigate(); const { name, @@ -78,6 +82,15 @@ export const BondedMixnode = ({ identityKey, host, } = mixnode; + + const getNextInterval = async () => { + try { + const { nextEpoch: newNextEpoch } = await getIntervalAsDate(); + setNextEpoch(newNextEpoch); + } catch { + setNextEpoch(Error()); + } + }; const cells: Cell[] = [ { cell: `${stake.amount} ${stake.denom}`, @@ -121,6 +134,10 @@ export const BondedMixnode = ({ }, ]; + useEffect(() => { + getNextInterval(); + }, []); + return ( - {name && ( - - - {name} - - + {name?.includes(textWhenNotName) ? null : ( + + {name} + )} - + + + + + } Action={ - isMixnode(mixnode) && ( - - - - - - ) + + {isMixnode(mixnode) && ( + + + + + + )} + {nextEpoch instanceof Error ? null : ( + + Next epoch starts at {nextEpoch} + + )} + } > diff --git a/nym-wallet/src/components/Bonding/forms/BondMixnodeForm.tsx b/nym-wallet/src/components/Bonding/forms/BondMixnodeForm.tsx index 6f229a403d..2f8864a017 100644 --- a/nym-wallet/src/components/Bonding/forms/BondMixnodeForm.tsx +++ b/nym-wallet/src/components/Bonding/forms/BondMixnodeForm.tsx @@ -9,8 +9,8 @@ import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from 'src/utils'; import { NodeTypeSelector, TokenPoolSelector } from 'src/components'; import { MixnodeAmount, MixnodeData } from 'src/pages/bonding/types'; import { ModalListItem } from 'src/components/Modals/ModalListItem'; -import { amountSchema, mixnodeValidationSchema } from './mixnodeValidationSchema'; import { AppContext } from 'src/context'; +import { amountSchema, mixnodeValidationSchema } from './mixnodeValidationSchema'; const NodeFormData = ({ mixnodeData, onNext }: { mixnodeData: MixnodeData; onNext: (data: any) => void }) => { const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); diff --git a/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts index c126d6c309..82de5cd3a2 100644 --- a/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts +++ b/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts @@ -60,6 +60,7 @@ export const amountSchema = Yup.object().shape({ operatorCost: Yup.object().shape({ amount: Yup.string() .required('An operating cost is required') + // eslint-disable-next-line .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; diff --git a/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts index aa5752f4aa..2289760520 100644 --- a/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts +++ b/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts @@ -40,6 +40,7 @@ const operatingCostAndPmValidation = { 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; diff --git a/nym-wallet/src/components/Bonding/modals/BondMoreModal.tsx b/nym-wallet/src/components/Bonding/modals/BondMoreModal.tsx index e2145e431e..e99315b467 100644 --- a/nym-wallet/src/components/Bonding/modals/BondMoreModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/BondMoreModal.tsx @@ -4,7 +4,7 @@ import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField' import { ModalListItem } from 'src/components/Modals/ModalListItem'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { DecCoin } from '@nymproject/types'; -import { TokenPoolSelector, TPoolOption } from 'src/components/TokenPoolSelector'; +import { TPoolOption } from 'src/components/TokenPoolSelector'; import { ConfirmTx } from 'src/components/ConfirmTX'; import { useGetFee } from 'src/hooks/useGetFee'; import { validateAmount } from 'src/utils'; diff --git a/nym-wallet/src/components/Bonding/modals/BondOversaturatedModal.tsx b/nym-wallet/src/components/Bonding/modals/BondOversaturatedModal.tsx index be3ea33869..86d6b990b9 100644 --- a/nym-wallet/src/components/Bonding/modals/BondOversaturatedModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/BondOversaturatedModal.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Stack, Typography } from '@mui/material'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; -export const BondOversaturatedModal: React.FC<{ +export const BondOversaturatedModal: FCWithChildren<{ open: boolean; saturationPercentage: string; onClose?: () => void; diff --git a/nym-wallet/src/components/Bonding/modals/ConfirmationModal.tsx b/nym-wallet/src/components/Bonding/modals/ConfirmationModal.tsx index 9a3c683b55..53439a8e8c 100644 --- a/nym-wallet/src/components/Bonding/modals/ConfirmationModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/ConfirmationModal.tsx @@ -14,6 +14,7 @@ export type ConfirmationDetailProps = { export const ConfirmationDetailsModal = ({ title, subtitle, + children, txUrl, status, onClose, @@ -23,6 +24,7 @@ export const ConfirmationDetailsModal = ({ onClose: () => void; sx?: SxProps; backdropProps?: object; + children?: React.ReactNode; }) => { if (status === 'error') { ; @@ -45,6 +47,7 @@ export const ConfirmationDetailsModal = ({ {title}
{subtitle} + {children} {txUrl && } diff --git a/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx b/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx index 3a8dca5576..0aa53e7368 100644 --- a/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx @@ -12,7 +12,7 @@ import { simulateUpdateMixnodeCostParams, simulateVestingUpdateMixnodeCostParams import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { FeeDetails } from '@nymproject/types'; -//Now we are using the node setting page instead of this modal +// Now we are using the node setting page instead of this modal export const NodeSettings = ({ currentPm, isVesting, @@ -108,7 +108,7 @@ export const NodeSettings = ({ okLabel="Next" onClose={onClose} > - + Set profit margin diff --git a/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx b/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx index 15b46b517b..41eeb45211 100644 --- a/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; +import { useEffect } from 'react'; import { Typography } from '@mui/material'; -import { useEffect, useState } from 'react'; import { TBondedGateway, TBondedMixnode } from 'src/context'; import { useGetFee } from 'src/hooks/useGetFee'; import { isGateway, isMixnode } from 'src/types'; diff --git a/nym-wallet/src/components/Buy/Tutorial.tsx b/nym-wallet/src/components/Buy/Tutorial.tsx index 316f033347..be669a38a5 100644 --- a/nym-wallet/src/components/Buy/Tutorial.tsx +++ b/nym-wallet/src/components/Buy/Tutorial.tsx @@ -1,10 +1,11 @@ -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { Button, Stack, Typography, Grid, useMediaQuery, useTheme } from '@mui/material'; -import { Tune as TuneIcon, BorderColor as BorderColorIcon, Paid as PaidIcon } from '@mui/icons-material'; +import { Tune as TuneIcon, BorderColor as BorderColorIcon } from '@mui/icons-material'; import { CoinMark } from '@nymproject/react/coins/CoinMark'; -import { NymCard, ClientAddress } from '../../components'; -import { SignMessageModal } from './SignMessageModal'; import { PoweredByBity } from 'src/svg-icons'; +import { AppContext } from 'src/context'; +import { NymCard, ClientAddress } from '..'; +import { SignMessageModal } from './SignMessageModal'; // TODO retrieve this value from env const EXCHANGE_URL = 'https://buy.nymtech.net'; @@ -51,6 +52,7 @@ const TutorialStep = ({ ); export const Tutorial = () => { + const { clientDetails } = useContext(AppContext); const [showSignModal, setShowSignModal] = useState(false); const theme = useTheme(); const showBorder = useMediaQuery(theme.breakpoints.up('md')); @@ -112,7 +114,8 @@ export const Tutorial = () => { icon={} text={ t.palette.nym.text.muted }}> - {`Send the defined BTC amount to Bity's address that's given to you. As soon as your BTC tx has 4 confirmations, Bity will send the purchased NYM tokens to your wallet.`} + {`Send the defined BTC amount to Bity's address that's given to you. As soon as your BTC tx has 4 + confirmations, Bity will send the purchased NYM tokens to your wallet.`} } /> @@ -123,7 +126,12 @@ export const Tutorial = () => { - diff --git a/nym-wallet/src/components/ClientAddress.tsx b/nym-wallet/src/components/ClientAddress.tsx index 14298b0349..7ab858306e 100644 --- a/nym-wallet/src/components/ClientAddress.tsx +++ b/nym-wallet/src/components/ClientAddress.tsx @@ -4,20 +4,16 @@ import { AppContext } from '../context/main'; import { CopyToClipboard } from './CopyToClipboard'; import { splice } from '../utils'; -const AddressTooltip: FC<{ visible?: boolean; address?: string; children: React.ReactElement }> = ({ - visible, - address, - children, -}) => { - if (!visible) { - return children; - } - if (!address) { - return children; +const AddressTooltip: FCWithChildren<{ visible?: boolean; address?: string }> = ({ visible, address, children }) => { + if (!visible || !address) { + // eslint-disable-next-line react/jsx-no-useless-fragment + return <>{children}; } + return ( - {children} + {/* eslint-disable-next-line react/jsx-no-useless-fragment */} + <>{children} ); }; diff --git a/nym-wallet/src/components/ConfirmTX.tsx b/nym-wallet/src/components/ConfirmTX.tsx index 01c5ff0743..2e3d386f83 100644 --- a/nym-wallet/src/components/ConfirmTX.tsx +++ b/nym-wallet/src/components/ConfirmTX.tsx @@ -15,7 +15,7 @@ const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: ob } : {}; -export const ConfirmTx: React.FC<{ +export const ConfirmTx: FCWithChildren<{ open: boolean; header: string; subheader?: string; @@ -24,6 +24,7 @@ export const ConfirmTx: React.FC<{ onClose?: () => void; onPrev: () => void; isStorybook?: boolean; + children?: React.ReactNode; }> = ({ open, fee, onConfirm, onClose, header, subheader, onPrev, children, isStorybook }) => { const theme = useTheme(); return ( diff --git a/nym-wallet/src/components/Delegation/DelegateBlocker.tsx b/nym-wallet/src/components/Delegation/DelegateBlocker.tsx index fc5f5144eb..816c293a2f 100644 --- a/nym-wallet/src/components/Delegation/DelegateBlocker.tsx +++ b/nym-wallet/src/components/Delegation/DelegateBlocker.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { SxProps } from '@mui/material'; import { SimpleModal } from '../Modals/SimpleModal'; -export const OverSaturatedBlockerModal: React.FC<{ +export const OverSaturatedBlockerModal: FCWithChildren<{ open: boolean; onClose?: () => void; header?: string; diff --git a/nym-wallet/src/components/Delegation/DelegateModal.tsx b/nym-wallet/src/components/Delegation/DelegateModal.tsx index a6c1bb0dbf..ad549e206a 100644 --- a/nym-wallet/src/components/Delegation/DelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegateModal.tsx @@ -18,7 +18,7 @@ import { ErrorModal } from '../Modals/ErrorModal'; const MIN_AMOUNT_TO_DELEGATE = 10; -export const DelegateModal: React.FC<{ +export const DelegateModal: FCWithChildren<{ open: boolean; onClose: () => void; onOk?: ( diff --git a/nym-wallet/src/components/Delegation/DelegationActions.tsx b/nym-wallet/src/components/Delegation/DelegationActions.tsx index 0784114d9d..2b9001de7c 100644 --- a/nym-wallet/src/components/Delegation/DelegationActions.tsx +++ b/nym-wallet/src/components/Delegation/DelegationActions.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; import { Box, Button, Stack, Tooltip, Typography } from '@mui/material'; -import { DelegationEventKind } from '@nymproject/types'; import { Delegate, Undelegate } from '../../svg-icons'; import { ActionsMenu, ActionsMenuItem } from '../ActionsMenu'; import { DelegateListItemPending } from './types'; @@ -10,7 +9,7 @@ export type DelegationListItemActions = 'delegate' | 'undelegate' | 'redeem' | ' const BUTTON_SIZE = '32px'; const MIN_WIDTH = '150px'; -export const DelegationActions: React.FC<{ +export const DelegationActions: FCWithChildren<{ onActionClick?: (action: DelegationListItemActions) => void; isPending?: DelegateListItemPending; disableRedeemingRewards?: boolean; @@ -64,7 +63,7 @@ export const DelegationActions: React.FC<{ ); }; -export const DelegationsActionsMenu: React.FC<{ +export const DelegationsActionsMenu: FCWithChildren<{ onActionClick?: (action: DelegationListItemActions) => void; disableRedeemingRewards?: boolean; disableDelegateMore?: boolean | null; diff --git a/nym-wallet/src/components/Delegation/DelegationItem.tsx b/nym-wallet/src/components/Delegation/DelegationItem.tsx index d800f9f487..c405c18d83 100644 --- a/nym-wallet/src/components/Delegation/DelegationItem.tsx +++ b/nym-wallet/src/components/Delegation/DelegationItem.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Chip, IconButton, TableCell, TableRow, Tooltip, Typography } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; import { decimalToPercentage, DelegationWithEverything } from '@nymproject/types'; +import { LockOutlined } from '@mui/icons-material'; import { isDelegation } from 'src/context/delegations'; import { toPercentIntegerString } from 'src/utils'; import { format } from 'date-fns'; @@ -12,6 +13,7 @@ const getStakeSaturation = (item: DelegationWithEverything) => !item.stake_saturation ? '-' : `${decimalToPercentage(item.stake_saturation)}%`; const getRewardValue = (item: DelegationWithEverything) => { + // eslint-disable-next-line @typescript-eslint/naming-convention const { unclaimed_rewards } = item; return !unclaimed_rewards ? '-' : `${unclaimed_rewards.amount} ${unclaimed_rewards.denom}`; }; @@ -29,15 +31,15 @@ export const DelegationItem = ({ }) => { const operatingCost = isDelegation(item) && item.cost_params?.interval_operating_cost; + const tooltipText = () => { + if (nodeIsUnbonded) { + return 'This node has unbonded and it does not exist anymore. You need to undelegate from it to get your stake and outstanding rewards (if any) back.'; + } + return ''; + }; + return ( - + {nodeIsUnbonded ? ( @@ -76,6 +78,13 @@ export const DelegationItem = ({ {getRewardValue(item)} + + {item.uses_vesting_contract_tokens && ( + + + + )} + {!item.pending_events.length && !nodeIsUnbonded && ( , property: string) => void; @@ -43,9 +46,10 @@ const headCells: HeadCell[] = [ { id: 'delegated_on_iso_datetime', label: 'Delegated on', sortable: true, align: 'left' }, { id: 'amount', label: 'Delegation', sortable: true, align: 'left' }, { id: 'unclaimed_rewards', label: 'Reward', sortable: true, align: 'left' }, + { id: 'uses_locked_tokens', label: '', sortable: false, align: 'left' }, ]; -const EnhancedTableHead: React.FC = ({ order, orderBy, onRequestSort }) => { +const EnhancedTableHead: FCWithChildren = ({ order, orderBy, onRequestSort }) => { const createSortHandler = (property: string) => (event: React.MouseEvent) => { onRequestSort(event, property); }; @@ -82,12 +86,13 @@ const EnhancedTableHead: React.FC = ({ order, orderBy, onReq ); }; -const sortByUnbondedMixnodeFirst = (a: DelegationWithEvent) => { +// Pin delegations on unbonded nodes to the top of the list +const sortByUnbondedMixnodeFirst = (a: any) => { if (!a.node_identity) return -1; return 1; }; -export const DelegationList: React.FC<{ +export const DelegationList: FCWithChildren<{ isLoading?: boolean; items: TDelegations; onItemActionClick?: (item: DelegationWithEverything, action: DelegationListItemActions) => void; @@ -95,21 +100,47 @@ export const DelegationList: React.FC<{ // eslint-disable-next-line @typescript-eslint/no-unused-vars }> = ({ isLoading, items, onItemActionClick, explorerUrl }) => { const [order, setOrder] = React.useState('asc'); - const [orderBy, setOrderBy] = React.useState('delegated_on_iso_datetime'); + const [orderBy, setOrderBy] = React.useState('delegated_on_iso_datetime'); - const handleRequestSort = (event: React.MouseEvent, property: string) => { + const handleRequestSort = (event: React.MouseEvent, property: any) => { const isAsc = orderBy === property && order === 'asc'; setOrder(isAsc ? 'desc' : 'asc'); setOrderBy(property); }; + // if sorting by either amount or unclaimed_rewards + // base sorting on their number counterparts + const mapOrderBy = (key: SortingKeys) => { + if (key === 'amount') return 'delegationValue'; + if (key === 'unclaimed_rewards') return 'operatorReward'; + if (key === 'profit_margin_percent') return 'profitMarginValue'; + if (key === 'operating_cost') return 'operatorCostValue'; + return key; + }; + + const mapAndSort = (_items: TDelegations) => { + const map = _items.map((item) => + isDelegation(item) + ? { + ...item, + delegationValue: Number(item.amount.amount), + operatorReward: Number(item.unclaimed_rewards?.amount), + profitMarginValue: Number(item.cost_params?.profit_margin_percent), + operatorCostValue: Number(item.cost_params?.interval_operating_cost), + } + : item, + ); + + return _orderBy(map, mapOrderBy(orderBy), order).sort(sortByUnbondedMixnodeFirst); + }; + return ( {items.length ? ( - items.sort(sortByUnbondedMixnodeFirst).map((item) => { + mapAndSort(items).map((item: any) => { if (isPendingDelegation(item)) return ; if (isDelegation(item)) return ( diff --git a/nym-wallet/src/components/Delegation/DelegationModal.stories.tsx b/nym-wallet/src/components/Delegation/DelegationModal.stories.tsx index 8694938a63..bba93fca5a 100644 --- a/nym-wallet/src/components/Delegation/DelegationModal.stories.tsx +++ b/nym-wallet/src/components/Delegation/DelegationModal.stories.tsx @@ -30,7 +30,7 @@ const transactionForDarkTheme = { hash: '11ED7B9E21534A9421834F52FED5103DC6E982949C06335F5E12EFC71DAF0CF0', }; -const Content: React.FC<{ children: React.ReactElement; handleClick: () => void }> = ({ +const Content: FCWithChildren<{ children: React.ReactElement; handleClick: () => void }> = ({ children, handleClick, }) => ( diff --git a/nym-wallet/src/components/Delegation/DelegationModal.tsx b/nym-wallet/src/components/Delegation/DelegationModal.tsx index e0facddf1d..adc4588c84 100644 --- a/nym-wallet/src/components/Delegation/DelegationModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegationModal.tsx @@ -36,12 +36,13 @@ export type DelegationModalProps = { }[]; }; -export const DelegationModal: React.FC< +export const DelegationModal: FCWithChildren< DelegationModalProps & { open: boolean; onClose: () => void; sx?: SxProps; backdropProps?: object; + children?: React.ReactNode; } > = ({ status, action, message, transactions, open, onClose, children, sx, backdropProps }) => { if (status === 'loading') return ; diff --git a/nym-wallet/src/components/Delegation/Delegations.tsx b/nym-wallet/src/components/Delegation/Delegations.tsx index 0a6781e44a..82c40c9e19 100644 --- a/nym-wallet/src/components/Delegation/Delegations.tsx +++ b/nym-wallet/src/components/Delegation/Delegations.tsx @@ -5,7 +5,7 @@ import { Link } from '@nymproject/react/link/Link'; import { DelegationList } from './DelegationList'; import { DelegationListItemActions } from './DelegationActions'; -export const Delegations: React.FC<{ +export const Delegations: FCWithChildren<{ isLoading?: boolean; items: DelegationWithEverything[]; explorerUrl: string; diff --git a/nym-wallet/src/components/Delegation/Modals.stories.tsx b/nym-wallet/src/components/Delegation/Modals.stories.tsx index 218194841b..630d023217 100644 --- a/nym-wallet/src/components/Delegation/Modals.stories.tsx +++ b/nym-wallet/src/components/Delegation/Modals.stories.tsx @@ -15,7 +15,7 @@ export default { title: 'Delegation/Components/Action Modals', }; -const Background: React.FC<{ onOpen: () => void }> = ({ onOpen }) => { +const Background: FCWithChildren<{ onOpen: () => void }> = ({ onOpen }) => { const theme = useTheme(); return ( diff --git a/nym-wallet/src/components/Delegation/UndelegateModal.tsx b/nym-wallet/src/components/Delegation/UndelegateModal.tsx index 99715d7425..fe518d37ff 100644 --- a/nym-wallet/src/components/Delegation/UndelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/UndelegateModal.tsx @@ -1,5 +1,4 @@ import { Box, SxProps } from '@mui/material'; -import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; import React, { useEffect } from 'react'; import { FeeDetails } from '@nymproject/types'; import { useGetFee } from 'src/hooks/useGetFee'; @@ -8,7 +7,7 @@ import { ModalFee } from '../Modals/ModalFee'; import { ModalListItem } from '../Modals/ModalListItem'; import { SimpleModal } from '../Modals/SimpleModal'; -export const UndelegateModal: React.FC<{ +export const UndelegateModal: FCWithChildren<{ open: boolean; onClose?: () => void; onOk?: (mixId: number, identityKey: string, usesVestingContractTokens: boolean, fee?: FeeDetails) => void; diff --git a/nym-wallet/src/components/LogViewer/index.tsx b/nym-wallet/src/components/LogViewer/index.tsx index f2a9d2e6d0..c7d4e69911 100644 --- a/nym-wallet/src/components/LogViewer/index.tsx +++ b/nym-wallet/src/components/LogViewer/index.tsx @@ -42,6 +42,7 @@ export const LogViewer: FC = () => { useEffect(() => { listen('log://log', (event) => { + // eslint-disable-next-line no-console console.log(event.payload); const payload = event.payload as RecordPayload; messages.current.unshift(payload); diff --git a/nym-wallet/src/components/Mnemonic.tsx b/nym-wallet/src/components/Mnemonic.tsx index d445172e9a..7e592c1902 100644 --- a/nym-wallet/src/components/Mnemonic.tsx +++ b/nym-wallet/src/components/Mnemonic.tsx @@ -1,23 +1,32 @@ import React from 'react'; -import { Button, Stack, TextField, Typography } from '@mui/material'; -import { Check, ContentCopySharp } from '@mui/icons-material'; +import { Box, Checkbox, FormControlLabel, Stack, TextField, Typography } from '@mui/material'; +import { Title } from 'src/pages/auth/components/heading'; import { Warning } from './Warning'; export const Mnemonic = ({ mnemonic, - copied, - handleCopy, + confirmed, + withTitle, + handleConfirmed, }: { mnemonic: string; - copied: boolean; - handleCopy: (text?: string) => void; + confirmed?: boolean; + withTitle?: boolean; + handleConfirmed?: (confirmed: boolean) => void; }) => ( - - - - Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future - - + + {withTitle && ( + + + </Box> + )} + <Box sx={{ pb: 2 }}> + <Warning> + <Typography sx={{ textAlign: 'center' }}> + Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future + </Typography> + </Warning> + </Box> <TextField label="Mnemonic" type="input" @@ -38,19 +47,11 @@ export const Mnemonic = ({ }} /> - <Button - color="inherit" - disableElevation - size="large" - onClick={() => { - handleCopy(mnemonic); - }} - sx={{ - width: 250, - }} - endIcon={!copied ? <ContentCopySharp /> : <Check color="success" />} - > - Copy mnemonic - </Button> + {handleConfirmed && ( + <FormControlLabel + label="I saved my mnemonic" + control={<Checkbox checked={confirmed} onChange={(_, checked) => handleConfirmed(checked)} />} + /> + )} </Stack> ); diff --git a/nym-wallet/src/components/Modals/ErrorModal.tsx b/nym-wallet/src/components/Modals/ErrorModal.tsx index 42d66c634a..d46c51d415 100644 --- a/nym-wallet/src/components/Modals/ErrorModal.tsx +++ b/nym-wallet/src/components/Modals/ErrorModal.tsx @@ -2,13 +2,14 @@ import React from 'react'; import { Box, Button, Modal, SxProps, Typography } from '@mui/material'; import { modalStyle } from './styles'; -export const ErrorModal: React.FC<{ +export const ErrorModal: FCWithChildren<{ open: boolean; title?: string; message?: string; sx?: SxProps; backdropProps?: object; onClose: () => void; + children?: React.ReactNode; }> = ({ children, open, title, message, sx, backdropProps, onClose }) => ( <Modal open={open} onClose={onClose} BackdropProps={backdropProps}> <Box diff --git a/nym-wallet/src/components/Modals/LoadingModal.tsx b/nym-wallet/src/components/Modals/LoadingModal.tsx index eda3d407a4..522d976f65 100644 --- a/nym-wallet/src/components/Modals/LoadingModal.tsx +++ b/nym-wallet/src/components/Modals/LoadingModal.tsx @@ -13,7 +13,7 @@ const modalStyle: SxProps = { p: 4, }; -export const LoadingModal: React.FC<{ +export const LoadingModal: FCWithChildren<{ sx?: SxProps; backdropProps?: object; }> = ({ sx, backdropProps }) => ( diff --git a/nym-wallet/src/components/Modals/ModalDivider.tsx b/nym-wallet/src/components/Modals/ModalDivider.tsx index 5bae3a8b50..6258e0bfac 100644 --- a/nym-wallet/src/components/Modals/ModalDivider.tsx +++ b/nym-wallet/src/components/Modals/ModalDivider.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Box, SxProps } from '@mui/material'; -export const ModalDivider: React.FC<{ +export const ModalDivider: FCWithChildren<{ sx?: SxProps; }> = ({ sx }) => <Box borderTop="1px solid" borderColor="rgba(141, 147, 153, 0.2)" my={1} sx={sx} />; diff --git a/nym-wallet/src/components/Modals/ModalFee.tsx b/nym-wallet/src/components/Modals/ModalFee.tsx index 752be087a7..835b73e689 100644 --- a/nym-wallet/src/components/Modals/ModalFee.tsx +++ b/nym-wallet/src/components/Modals/ModalFee.tsx @@ -27,11 +27,9 @@ export const ModalFee = ({ fee, isLoading, error, divider }: TFeeProps) => ( </> ); -export const ModalTotalAmount = ({ fee, amount, isLoading, error, divider }: TTotalAmountProps) => { - return ( - <> - <ModalListItem label="Total amount" value={getValue({ fee, amount, isLoading, error })} fontWeight={600} /> - {divider && <ModalDivider />} - </> - ); -}; +export const ModalTotalAmount = ({ fee, amount, isLoading, error, divider }: TTotalAmountProps) => ( + <> + <ModalListItem label="Total amount" value={getValue({ fee, amount, isLoading, error })} fontWeight={600} /> + {divider && <ModalDivider />} + </> +); diff --git a/nym-wallet/src/components/Modals/ModalListItem.tsx b/nym-wallet/src/components/Modals/ModalListItem.tsx index 1bf70c2384..830c25705e 100644 --- a/nym-wallet/src/components/Modals/ModalListItem.tsx +++ b/nym-wallet/src/components/Modals/ModalListItem.tsx @@ -1,22 +1,28 @@ import React from 'react'; -import { Box, Stack, Typography, TypographyProps } from '@mui/material'; +import { Box, Stack, SxProps, Typography, TypographyProps } from '@mui/material'; import { ModalDivider } from './ModalDivider'; -export const ModalListItem: React.FC<{ +export const ModalListItem: FCWithChildren<{ label: string; divider?: boolean; hidden?: boolean; fontWeight?: TypographyProps['fontWeight']; + fontSize?: TypographyProps['fontSize']; light?: boolean; value?: React.ReactNode; -}> = ({ label, value, hidden, fontWeight, divider }) => ( + sxValue?: SxProps; +}> = ({ label, value, hidden, fontWeight, fontSize, divider, sxValue }) => ( <Box sx={{ display: hidden ? 'none' : 'block' }}> - <Stack direction="row" justifyContent="space-between"> + <Stack direction="row" justifyContent="space-between" alignItems="center"> <Typography fontSize="smaller" fontWeight={fontWeight} sx={{ color: 'text.primary', fontSize: 14 }}> {label} </Typography> {value && ( - <Typography fontSize="smaller" fontWeight={fontWeight} sx={{ color: 'text.primary', fontSize: 14 }}> + <Typography + fontSize="smaller" + fontWeight={fontWeight} + sx={{ color: 'text.primary', fontSize: fontSize || 14, ...sxValue }} + > {value} </Typography> )} diff --git a/nym-wallet/src/components/Modals/SimpleModal.stories.tsx b/nym-wallet/src/components/Modals/SimpleModal.stories.tsx index 994cf7eb48..921e9a3c5a 100644 --- a/nym-wallet/src/components/Modals/SimpleModal.stories.tsx +++ b/nym-wallet/src/components/Modals/SimpleModal.stories.tsx @@ -16,7 +16,7 @@ export default { component: SimpleModal, } as ComponentMeta<typeof SimpleModal>; -const BasePage: React.FC<{ children: React.ReactElement<any, any>; handleClick: () => void }> = ({ +const BasePage: FCWithChildren<{ children: React.ReactElement<any, any>; handleClick: () => void }> = ({ children, handleClick, }) => ( diff --git a/nym-wallet/src/components/Modals/SimpleModal.tsx b/nym-wallet/src/components/Modals/SimpleModal.tsx index 591354ffe5..7fdeb328b2 100644 --- a/nym-wallet/src/components/Modals/SimpleModal.tsx +++ b/nym-wallet/src/components/Modals/SimpleModal.tsx @@ -6,7 +6,7 @@ import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import { StyledBackButton } from 'src/components/StyledBackButton'; import { modalStyle } from './styles'; -export const SimpleModal: React.FC<{ +export const SimpleModal: FCWithChildren<{ open: boolean; hideCloseIcon?: boolean; displayErrorIcon?: boolean; @@ -25,6 +25,7 @@ export const SimpleModal: React.FC<{ okDisabled?: boolean; sx?: SxProps; backdropProps?: object; + children?: React.ReactNode; }> = ({ open, hideCloseIcon, diff --git a/nym-wallet/src/components/NetworkSelector.tsx b/nym-wallet/src/components/NetworkSelector.tsx index 00da844d3b..d17330f679 100644 --- a/nym-wallet/src/components/NetworkSelector.tsx +++ b/nym-wallet/src/components/NetworkSelector.tsx @@ -11,7 +11,7 @@ const networks: { networkName: Network; name: string }[] = [ { networkName: 'QA', name: 'QA' }, ]; -const NetworkItem: React.FC<{ title: string; isSelected: boolean; onSelect: () => void }> = ({ +const NetworkItem: FCWithChildren<{ title: string; isSelected: boolean; onSelect: () => void }> = ({ title, isSelected, onSelect, diff --git a/nym-wallet/src/components/NymCard.tsx b/nym-wallet/src/components/NymCard.tsx index d3377a5fa9..768f0811ab 100644 --- a/nym-wallet/src/components/NymCard.tsx +++ b/nym-wallet/src/components/NymCard.tsx @@ -10,7 +10,7 @@ const CardContentNoPadding = styled(CardContent)(() => ({ }, })); -export const NymCard: React.FC<{ +export const NymCard: FCWithChildren<{ title: string | React.ReactElement; subheader?: string | React.ReactChild; Action?: React.ReactNode; @@ -20,6 +20,7 @@ export const NymCard: React.FC<{ dataTestid?: string; sx?: SxProps; sxTitle?: SxProps; + children: React.ReactNode; }> = ({ title, subheader, Action, Icon, noPadding, borderless, children, dataTestid, sx, sxTitle }) => ( <Card variant="outlined" sx={{ overflow: 'auto', ...(borderless && { border: 'none', dropShadow: 'none' }), ...sx }}> <CardHeader diff --git a/nym-wallet/src/components/Rewards/RedeemModal.stories.tsx b/nym-wallet/src/components/Rewards/RedeemModal.stories.tsx index 875c347689..0cf6e56567 100644 --- a/nym-wallet/src/components/Rewards/RedeemModal.stories.tsx +++ b/nym-wallet/src/components/Rewards/RedeemModal.stories.tsx @@ -15,7 +15,7 @@ export default { component: RedeemModal, } as ComponentMeta<typeof RedeemModal>; -const Content: React.FC<{ +const Content: FCWithChildren<{ setOpen: (value: boolean) => void; }> = ({ setOpen }) => ( <Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}> diff --git a/nym-wallet/src/components/Rewards/RedeemModal.tsx b/nym-wallet/src/components/Rewards/RedeemModal.tsx index 07d361aec0..9d80a5d048 100644 --- a/nym-wallet/src/components/Rewards/RedeemModal.tsx +++ b/nym-wallet/src/components/Rewards/RedeemModal.tsx @@ -9,7 +9,7 @@ import { SimpleModal } from '../Modals/SimpleModal'; import { FeeWarning } from '../FeeWarning'; import { ModalListItem } from '../Modals/ModalListItem'; -export const RedeemModal: React.FC<{ +export const RedeemModal: FCWithChildren<{ open: boolean; onClose?: () => void; onOk?: (mixId: number, identityKey: string, fee?: FeeDetails) => void; diff --git a/nym-wallet/src/components/Rewards/RewardsSummary.tsx b/nym-wallet/src/components/Rewards/RewardsSummary.tsx index 3cbc7b1fc5..aa25ce8af9 100644 --- a/nym-wallet/src/components/Rewards/RewardsSummary.tsx +++ b/nym-wallet/src/components/Rewards/RewardsSummary.tsx @@ -3,7 +3,7 @@ import { CircularProgress, Stack, Typography } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import { InfoTooltip } from '../InfoToolTip'; -export const RewardsSummary: React.FC<{ +export const RewardsSummary: FCWithChildren<{ isLoading?: boolean; totalDelegation?: string; totalRewards?: string; diff --git a/nym-wallet/src/components/RewardsPlayground/InclusionProbability.tsx b/nym-wallet/src/components/RewardsPlayground/InclusionProbability.tsx new file mode 100644 index 0000000000..d60f7886c7 --- /dev/null +++ b/nym-wallet/src/components/RewardsPlayground/InclusionProbability.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Typography } from '@mui/material'; +import { SelectionChance } from '@nymproject/types'; + +const colorMap: { [key in SelectionChance]: string } = { + Low: 'error.main', + Good: 'warning.main', + High: 'success.main', +}; + +const textMap: { [key in SelectionChance]: string } = { + Low: 'Low', + Good: 'Good', + High: 'High', +}; + +export const InclusionProbability = ({ probability }: { probability: SelectionChance }) => ( + <Typography sx={{ color: colorMap[probability] }}>{textMap[probability]}</Typography> +); diff --git a/nym-wallet/src/components/RewardsPlayground/Inputs.tsx b/nym-wallet/src/components/RewardsPlayground/Inputs.tsx new file mode 100644 index 0000000000..1f1dbac935 --- /dev/null +++ b/nym-wallet/src/components/RewardsPlayground/Inputs.tsx @@ -0,0 +1,91 @@ +import React, { useCallback } from 'react'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Button, Grid, TextField, Typography } from '@mui/material'; +import { useForm } from 'react-hook-form'; +import { DefaultInputValues } from 'src/pages/bonding/node-settings/apy-playground'; +import { inputValidationSchema } from './inputsValidationSchema'; + +export type InputFields = { + label: string; + name: 'profitMargin' | 'uptime' | 'bond' | 'delegations' | 'operatorCost' | 'uptime'; + isPercentage?: boolean; +}[]; + +export type CalculateArgs = { + bond: string; + delegations: string; + uptime: string; + profitMargin: string; + operatorCost: string; +}; + +const inputFields: InputFields = [ + { label: 'Profit margin', name: 'profitMargin', isPercentage: true }, + { label: 'Operator cost', name: 'operatorCost' }, + { label: 'Bond', name: 'bond' }, + { label: 'Delegations', name: 'delegations' }, + { label: 'Uptime', name: 'uptime', isPercentage: true }, +]; + +export const Inputs = ({ + onCalculate, + defaultValues, +}: { + onCalculate: (args: CalculateArgs) => Promise<void>; + defaultValues: DefaultInputValues; +}) => { + const handleCalculate = useCallback( + async (args: CalculateArgs) => { + onCalculate({ + bond: args.bond, + delegations: args.delegations, + uptime: args.uptime, + profitMargin: args.profitMargin, + operatorCost: args.operatorCost, + }); + }, + [onCalculate], + ); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(inputValidationSchema), + defaultValues, + }); + + return ( + <Grid container spacing={3}> + {inputFields.map((field) => ( + <Grid item xs={12} lg={2} key={field.name}> + <TextField + {...register(field.name)} + fullWidth + label={field.label} + name={field.name} + error={Boolean(errors[field.name])} + helperText={errors[field.name]?.message} + InputProps={{ + endAdornment: <Typography sx={{ color: 'grey.600' }}>{field.isPercentage ? '%' : 'NYM'}</Typography>, + }} + InputLabelProps={{ shrink: true }} + /> + </Grid> + ))}{' '} + <Grid item xs={12} lg={2}> + <Button + variant="contained" + disableElevation + onClick={handleSubmit(handleCalculate)} + size="large" + fullWidth + disabled={Boolean(Object.keys(errors).length)} + > + Calculate + </Button> + </Grid> + </Grid> + ); +}; diff --git a/nym-wallet/src/components/RewardsPlayground/NodeDetail.tsx b/nym-wallet/src/components/RewardsPlayground/NodeDetail.tsx new file mode 100644 index 0000000000..d067dc3e5b --- /dev/null +++ b/nym-wallet/src/components/RewardsPlayground/NodeDetail.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Card, CardContent, Divider, Stack, Typography } from '@mui/material'; +import { SelectionChance } from '@nymproject/types'; +import { InclusionProbability } from './InclusionProbability'; + +const computeSelectionProbability = (saturation: number): SelectionChance => { + if (saturation < 5) return 'Low'; + + if (saturation > 5 && saturation < 15) return 'Good'; + + return 'High'; +}; + +export const NodeDetails = ({ saturation }: { saturation?: string }) => { + if (!saturation) return null; + + return ( + <Card variant="outlined" sx={{ p: 1 }}> + <CardContent> + <Stack direction="row" justifyContent="space-between"> + <Typography fontWeight="medium">Stake saturation</Typography> + <Typography>{saturation || '- '}%</Typography> + </Stack> + <Divider sx={{ my: 1 }} /> + <Stack direction="row" justifyContent="space-between"> + <Typography fontWeight="medium">Selection probability</Typography> + <InclusionProbability probability={computeSelectionProbability(parseInt(saturation, 10))} /> + </Stack> + </CardContent> + </Card> + ); +}; diff --git a/nym-wallet/src/components/RewardsPlayground/ResultsTable.tsx b/nym-wallet/src/components/RewardsPlayground/ResultsTable.tsx new file mode 100644 index 0000000000..19196c176d --- /dev/null +++ b/nym-wallet/src/components/RewardsPlayground/ResultsTable.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { + Card, + CardContent, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from '@mui/material'; + +export type Results = { + operator: { + daily: string; + monthly: string; + yearly: string; + }; + delegator: { + daily: string; + monthly: string; + yearly: string; + }; + total: { + daily: string; + monthly: string; + yearly: string; + }; +}; + +const tableHeader = [ + { title: 'Estimated rewards', bold: true }, + { title: 'Per day' }, + { title: 'Per month' }, + { title: 'Per year' }, +]; + +export const ResultsTable = ({ results }: { results: Results }) => { + const tableRows = [ + { title: 'Total node reward', ...results.total }, + { title: 'Operator rewards', ...results.operator }, + { title: 'Delegator rewards', ...results.delegator }, + ]; + + return ( + <Card variant="outlined" sx={{ p: 1 }}> + <CardContent> + <TableContainer> + <Table> + <TableHead> + <TableRow> + {tableHeader.map((header) => ( + <TableCell> + <Typography fontWeight={header.bold ? 'bold' : 'regular'}>{header.title}</Typography> + </TableCell> + ))} + </TableRow> + </TableHead> + <TableBody> + {tableRows.map((row) => ( + <TableRow> + <TableCell>{row.title}</TableCell> + <TableCell>{row.daily}</TableCell> + <TableCell>{row.monthly}</TableCell> + <TableCell>{row.yearly}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + </CardContent> + </Card> + ); +}; diff --git a/nym-wallet/src/components/RewardsPlayground/inputsValidationSchema.ts b/nym-wallet/src/components/RewardsPlayground/inputsValidationSchema.ts new file mode 100644 index 0000000000..2e44ec4cb5 --- /dev/null +++ b/nym-wallet/src/components/RewardsPlayground/inputsValidationSchema.ts @@ -0,0 +1,43 @@ +import * as Yup from 'yup'; +import { isGreaterThan, isLessThan } from 'src/utils'; + +export const inputValidationSchema = Yup.object().shape({ + profitMargin: Yup.string() + .required('profit margin is a required field') + .test('Is valid profit margin value', (value, ctx) => { + const stringValueToNumber = Math.round(Number(value)); + + if (isGreaterThan(stringValueToNumber, -1) && isLessThan(stringValueToNumber, 101)) return true; + return ctx.createError({ message: 'Profit margin must be a number from 0 and 100' }); + }), + uptime: Yup.string() + .required() + .test('Is valid uptime value', (value, ctx) => { + const stringValueToNumber = Math.round(Number(value)); + if (stringValueToNumber && isGreaterThan(stringValueToNumber, 0) && isLessThan(stringValueToNumber, 101)) + return true; + return ctx.createError({ message: 'Uptime must be a number between 0 and 100' }); + }), + bond: Yup.string() + .required() + .test('Is valid bond value', (value, ctx) => { + if (Number(value)) return true; + return ctx.createError({ message: 'Bond must be a valid number' }); + }), + delegations: Yup.string() + .required() + .test('Is valid delegation value', (value, ctx) => { + if (Number(value)) return true; + return ctx.createError({ message: 'Delegations must be a valid number' }); + }), + operatorCost: Yup.string() + .required('operator cost is a required field') + .test('Is valid operator cost value', (value, ctx) => { + const stringValueToNumber = Math.round(Number(value)); + + if (isLessThan(stringValueToNumber, 0)) + return ctx.createError({ message: 'Operator cost must be a valid number' }); + + return true; + }), +}); diff --git a/nym-wallet/src/components/Send/SendDetails.stories.tsx b/nym-wallet/src/components/Send/SendDetails.stories.tsx index 88e231a2b6..ced7ee44ce 100644 --- a/nym-wallet/src/components/Send/SendDetails.stories.tsx +++ b/nym-wallet/src/components/Send/SendDetails.stories.tsx @@ -35,6 +35,9 @@ export const SendInput = () => { onClose={() => {}} onAddressChange={() => {}} onAmountChange={() => {}} + onUserFeesChange={() => {}} + onMemoChange={() => {}} + setShowMore={() => {}} {...storybookStylesModal(theme)} /> ); diff --git a/nym-wallet/src/components/Send/SendDetailsModal.tsx b/nym-wallet/src/components/Send/SendDetailsModal.tsx index f2b7d13491..366bf7ba9c 100644 --- a/nym-wallet/src/components/Send/SendDetailsModal.tsx +++ b/nym-wallet/src/components/Send/SendDetailsModal.tsx @@ -16,6 +16,7 @@ export const SendDetailsModal = ({ onSend, sx, backdropProps, + memo, }: { fromAddress?: string; toAddress: string; @@ -27,6 +28,7 @@ export const SendDetailsModal = ({ onSend: (data: { val: DecCoin; to: string }) => void; sx?: SxProps; backdropProps?: object; + memo?: string; }) => ( <SimpleModal header="Send details" @@ -43,6 +45,20 @@ export const SendDetailsModal = ({ <ModalListItem label="To" value={toAddress} divider /> <ModalListItem label="Amount" value={`${amount?.amount} ${denom.toUpperCase()}`} divider /> <ModalFee fee={fee} divider isLoading={false} /> + {memo && ( + <ModalListItem + label="Memo" + value={memo} + sxValue={{ + textOverflow: 'ellipsis', + overflow: 'hidden', + whiteSpace: 'nowrap', + overflowWrap: 'anywhere', + maxWidth: '300px', + }} + divider + /> + )} <ModalTotalAmount fee={fee} amount={amount?.amount} divider isLoading={false} /> </Stack> </SimpleModal> diff --git a/nym-wallet/src/components/Send/SendErrorModal.tsx b/nym-wallet/src/components/Send/SendErrorModal.tsx index 18fb5b4b74..7c0adfa3e0 100644 --- a/nym-wallet/src/components/Send/SendErrorModal.tsx +++ b/nym-wallet/src/components/Send/SendErrorModal.tsx @@ -1,15 +1,17 @@ import React from 'react'; -import { SxProps } from '@mui/material'; +import { SxProps, Typography } from '@mui/material'; import { SimpleModal } from '../Modals/SimpleModal'; export const SendErrorModal = ({ onClose, sx, backdropProps, + error, }: { onClose: () => void; sx?: SxProps; backdropProps?: {}; + error?: string; }) => ( <SimpleModal open @@ -30,5 +32,7 @@ export const SendErrorModal = ({ }} subHeaderStyles={{ textAlign: 'center', color: 'text.primary', fontSize: 14, fontWeight: 400 }} backdropProps={backdropProps} - /> + > + <Typography variant="body2">{error}</Typography> + </SimpleModal> ); diff --git a/nym-wallet/src/components/Send/SendInputModal.tsx b/nym-wallet/src/components/Send/SendInputModal.tsx index a54541d040..ca4d865f4c 100644 --- a/nym-wallet/src/components/Send/SendInputModal.tsx +++ b/nym-wallet/src/components/Send/SendInputModal.tsx @@ -1,11 +1,15 @@ import React, { useEffect, useState } from 'react'; -import { Stack, TextField, Typography, SxProps } from '@mui/material'; +import { Stack, TextField, Typography, SxProps, FormControlLabel, Checkbox } from '@mui/material'; +import Big from 'big.js'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; -import { CurrencyDenom, DecCoin } from '@nymproject/types'; +import { CurrencyDenom, DecCoin, isValidRawCoin } from '@nymproject/types'; import { validateAmount } from 'src/utils'; import { SimpleModal } from '../Modals/SimpleModal'; import { ModalListItem } from '../Modals/ModalListItem'; +const maxUserFees = '10.0'; +const minUserFees = '0.000001'; // aka 1 unym + export const SendInputModal = ({ fromAddress, toAddress, @@ -19,6 +23,12 @@ export const SendInputModal = ({ onAddressChange, sx, backdropProps, + userFees, + memo, + onUserFeesChange, + onMemoChange, + showMore, + setShowMore, }: { fromAddress?: string; toAddress: string; @@ -26,24 +36,61 @@ export const SendInputModal = ({ balance?: string; denom?: CurrencyDenom; error?: string; + showMore?: boolean; + setShowMore: (show: boolean) => void; onNext: () => void; onClose: () => void; onAmountChange: (value: DecCoin) => void; onAddressChange: (value: string) => void; sx?: SxProps; backdropProps?: object; + userFees?: DecCoin; + memo?: string; + onUserFeesChange: (value: DecCoin) => void; + onMemoChange: (value: string) => void; }) => { const [isValid, setIsValid] = useState(false); + const [memoIsValid, setMemoIsValid] = useState(true); + const [feeAmountIsValid, setFeeAmountIsValid] = useState(true); const validate = async (value: DecCoin) => { const isValidAmount = await validateAmount(value.amount, '0'); setIsValid(isValidAmount); }; + const validateUserFees = (fees: DecCoin) => { + if (!isValidRawCoin(fees.amount) || !Number(fees.amount)) { + setFeeAmountIsValid(false); + return; + } + const f = Big(fees.amount); + if (f.gt(maxUserFees) || f.lt(minUserFees)) { + setFeeAmountIsValid(false); + return; + } + setFeeAmountIsValid(true); + }; + useEffect(() => { if (amount) validate(amount); }, []); + useEffect(() => { + if (memo && !/^(\w|\s)+$/.test(memo)) { + setMemoIsValid(false); + return; + } + setMemoIsValid(true); + }, [memo]); + + useEffect(() => { + if (userFees) { + validateUserFees(userFees); + } else { + setFeeAmountIsValid(true); + } + }, [userFees]); + return ( <SimpleModal header="Send" @@ -51,7 +98,7 @@ export const SendInputModal = ({ onClose={onClose} okLabel="Next" onOk={async () => onNext()} - okDisabled={!isValid} + okDisabled={!isValid || !memoIsValid || !feeAmountIsValid} sx={sx} backdropProps={backdropProps} > @@ -84,6 +131,35 @@ export const SendInputModal = ({ Est. fee for this transaction will be show on the next page </Typography> </Stack> + <FormControlLabel + control={<Checkbox onChange={() => setShowMore(!showMore)} checked={showMore} />} + label="More options" + sx={{ mt: 2 }} + /> + {showMore && ( + <Stack direction="column" gap={3} mt={2} mb={3}> + <CurrencyFormField + label="Fees" + onChanged={(v) => onUserFeesChange(v)} + initialValue={userFees?.amount} + fullWidth + /> + <TextField + name="memo" + label="Memo" + onChange={(e) => onMemoChange(e.target.value)} + value={memo} + error={!memoIsValid} + helperText={ + !memoIsValid + ? ' The text is invalid, only alphanumeric characters and white spaces are allowed' + : undefined + } + InputLabelProps={{ shrink: true }} + fullWidth + /> + </Stack> + )} </SimpleModal> ); }; diff --git a/nym-wallet/src/components/Send/SendModal.tsx b/nym-wallet/src/components/Send/SendModal.tsx index c1792c7354..650fa4124c 100644 --- a/nym-wallet/src/components/Send/SendModal.tsx +++ b/nym-wallet/src/components/Send/SendModal.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { DecCoin } from '@nymproject/types'; import { AppContext, urls } from 'src/context'; import { useGetFee } from 'src/hooks/useGetFee'; @@ -18,18 +18,42 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void const [modal, setModal] = useState<'send' | 'send details'>('send'); const [error, setError] = useState<string>(); const [sendError, setSendError] = useState(false); + const [gasError, setGasError] = useState<string>(); const [isLoading, setIsLoading] = useState(false); + const [userFees, setUserFees] = useState<DecCoin>(); + const [memo, setMemo] = useState<string>(); const [txDetails, setTxDetails] = useState<TTransactionDetails>(); + const [showMoreOptions, setShowMoreOptions] = useState(false); const { clientDetails, userBalance, network } = useContext(AppContext); - const { fee, getFee } = useGetFee(); + const { fee, getFee, feeError, setFeeManually } = useGetFee(); + + useEffect(() => { + if (userFees?.amount.length === 0) { + setUserFees(undefined); + } + }, [userFees]); + + useEffect(() => { + if (!showMoreOptions) { + setUserFees(undefined); + setMemo(undefined); + } + }, [showMoreOptions]); + + // removes any zero-width spaces and trailing white space + const sanitizeAddress = (address: string) => address.replace(/[\u200B-\u200D\uFEFF]/g, '').trim(); const handleOnNext = async () => { if (amount) { setIsLoading(true); setError(undefined); try { - await getFee(simulateSend, { address: toAddress, amount }); + if (userFees) { + await setFeeManually(userFees); + } else { + await getFee(simulateSend, { address: toAddress, amount }); + } setModal('send details'); } catch (e) { setError(e as string); @@ -45,14 +69,18 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void setIsLoading(true); setError(undefined); try { - const txResponse = await send({ amount: val, address: to, memo: '', fee: fee?.fee }); + const txResponse = await send({ amount: val, address: to, memo: memo || '', fee: fee?.fee }); setTxDetails({ amount: `${amount?.amount} ${clientDetails?.display_mix_denom.toUpperCase()}`, txUrl: `${urls(network).blockExplorer}/transaction/${txResponse.tx_hash}`, }); } catch (e) { Console.error(e as string); - setSendError(true); + if (/Raw log: out of gas/.test(e as string)) { + setGasError('Out of gas, please increase the amount of fees'); + } else { + setSendError(true); + } } finally { setIsLoading(false); } @@ -60,7 +88,11 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void if (isLoading) return <LoadingModal />; - if (sendError) return <SendErrorModal onClose={onClose} />; + if (sendError) return <SendErrorModal onClose={onClose} error={feeError} />; + + if (gasError) { + return <SendErrorModal onClose={onClose} error={gasError} />; + } if (txDetails) return <SendSuccessModal txDetails={txDetails} onClose={onClose} />; @@ -75,6 +107,7 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void onPrev={() => setModal('send')} onSend={handleSend} denom={clientDetails?.display_mix_denom || 'nym'} + memo={memo} {...hasStorybookStyles} /> ); @@ -89,8 +122,14 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void onNext={handleOnNext} error={error} denom={clientDetails?.display_mix_denom} + userFees={userFees} + memo={memo} + showMore={showMoreOptions} onAmountChange={(value) => setAmount(value)} - onAddressChange={(value) => setToAddress(value)} + onAddressChange={(value) => setToAddress(sanitizeAddress(value))} + onUserFeesChange={(value) => setUserFees(value)} + onMemoChange={(value) => setMemo(value)} + setShowMore={setShowMoreOptions} {...hasStorybookStyles} /> ); diff --git a/nym-wallet/src/components/Send/SendSuccessModal.tsx b/nym-wallet/src/components/Send/SendSuccessModal.tsx index cee7c2fdff..ffee6dd2e2 100644 --- a/nym-wallet/src/components/Send/SendSuccessModal.tsx +++ b/nym-wallet/src/components/Send/SendSuccessModal.tsx @@ -1,9 +1,9 @@ import React, { useContext } from 'react'; import { Stack, Typography, SxProps } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; +import { AppContext } from 'src/context'; import { TTransactionDetails } from './types'; import { ConfirmationModal } from '../Modals/ConfirmationModal'; -import { AppContext } from 'src/context'; export const SendSuccessModal = ({ txDetails, diff --git a/nym-wallet/src/components/SuccessResponse.tsx b/nym-wallet/src/components/SuccessResponse.tsx index 2f3e2d4075..e8e9451c46 100644 --- a/nym-wallet/src/components/SuccessResponse.tsx +++ b/nym-wallet/src/components/SuccessResponse.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Stack, Typography } from '@mui/material'; -export const SuccessReponse: React.FC<{ +export const SuccessReponse: FCWithChildren<{ title: string; subtitle: string | React.ReactNode; caption: string | React.ReactNode; diff --git a/nym-wallet/src/components/Title.tsx b/nym-wallet/src/components/Title.tsx index 530b9deed8..fbc0997c3e 100644 --- a/nym-wallet/src/components/Title.tsx +++ b/nym-wallet/src/components/Title.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Box, SxProps, Typography } from '@mui/material'; -export const Title: React.FC<{ title: string | React.ReactNode; Icon?: React.ReactNode; sx?: SxProps }> = ({ +export const Title: FCWithChildren<{ title: string | React.ReactNode; Icon?: React.ReactNode; sx?: SxProps }> = ({ title, Icon, sx, diff --git a/nym-wallet/src/components/TokenPoolSelector.tsx b/nym-wallet/src/components/TokenPoolSelector.tsx index b289e90392..1ef768bbb3 100644 --- a/nym-wallet/src/components/TokenPoolSelector.tsx +++ b/nym-wallet/src/components/TokenPoolSelector.tsx @@ -4,7 +4,7 @@ import { AppContext } from '../context/main'; export type TPoolOption = 'balance' | 'locked'; -export const TokenPoolSelector: React.FC<{ disabled: boolean; onSelect: (pool: TPoolOption) => void }> = ({ +export const TokenPoolSelector: FCWithChildren<{ disabled: boolean; onSelect: (pool: TPoolOption) => void }> = ({ disabled, onSelect, }) => { diff --git a/nym-wallet/src/components/TransactionDetails.tsx b/nym-wallet/src/components/TransactionDetails.tsx index 6efbe2747d..7136a78db5 100644 --- a/nym-wallet/src/components/TransactionDetails.tsx +++ b/nym-wallet/src/components/TransactionDetails.tsx @@ -3,7 +3,7 @@ import { Card, Grid, Typography } from '@mui/material'; export type TTransactionDetails = Array<{ primary: string; secondary: string }>; -export const TransactionDetails: React.FC<{ details: TTransactionDetails }> = ({ details }) => ( +export const TransactionDetails: FCWithChildren<{ details: TTransactionDetails }> = ({ details }) => ( <Card variant="outlined" sx={{ width: '100%', p: 2 }}> {details.map(({ primary, secondary }, i) => ( <Grid container sx={{ mt: i !== 0 ? 1 : 0 }} key={primary}> diff --git a/nym-wallet/src/components/textfields.tsx b/nym-wallet/src/components/textfields.tsx index afcbcfedb7..94cc2577a6 100644 --- a/nym-wallet/src/components/textfields.tsx +++ b/nym-wallet/src/components/textfields.tsx @@ -5,7 +5,7 @@ import FormControlLabel from '@mui/material/FormControlLabel'; import Checkbox from '@mui/material/Checkbox'; import { Error } from './Error'; -export const MnemonicInput: React.FC<{ +export const MnemonicInput: FCWithChildren<{ mnemonic: string; error?: string; onUpdateMnemonic: (mnemonic: string) => void; @@ -37,7 +37,7 @@ export const MnemonicInput: React.FC<{ ); }; -export const PasswordInput: React.FC<{ +export const PasswordInput: FCWithChildren<{ password: string; error?: string; label?: string; diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index 9725a9570c..e3937ff8ad 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -28,7 +28,7 @@ export type TAccountMnemonic = { value?: string; accountName?: string }; export const AccountsContext = createContext({} as TAccounts); -export const AccountsProvider: React.FC = ({ children }) => { +export const AccountsProvider: FCWithChildren = ({ children }) => { const [accounts, setAccounts] = useState<AccountEntry[]>([]); const [selectedAccount, setSelectedAccount] = useState<AccountEntry>(); const [accountToEdit, setAccountToEdit] = useState<AccountEntry>(); diff --git a/nym-wallet/src/context/auth.tsx b/nym-wallet/src/context/auth.tsx index 12798ef134..1172e93f1b 100644 --- a/nym-wallet/src/context/auth.tsx +++ b/nym-wallet/src/context/auth.tsx @@ -21,7 +21,7 @@ const mnemonicToArray = (mnemonic: string): TMnemonicWords => .split(' ') .reduce((a, c: string, index) => [...a, { name: c, index: index + 1, disabled: false }], [] as TMnemonicWords); -export const AuthProvider: React.FC = ({ children }) => { +export const AuthProvider: FCWithChildren = ({ children }) => { const [password, setPassword] = useState(''); const [mnemonic, setMnemonic] = useState(''); const [mnemonicWords, setMnemonicWords] = useState<TMnemonicWords>([]); diff --git a/nym-wallet/src/context/bonding.tsx b/nym-wallet/src/context/bonding.tsx index 7225360456..48d8d8409d 100644 --- a/nym-wallet/src/context/bonding.tsx +++ b/nym-wallet/src/context/bonding.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import { FeeDetails, DecCoin, @@ -24,7 +25,6 @@ import { vestingBondMixNode, vestingUnbondGateway, vestingUnbondMixnode, - getPendingEpochEvents, updateMixnodeCostParams as updateMixnodeCostParamsRequest, vestingUpdateMixnodeCostParams as updateMixnodeVestingCostParamsRequest, getNodeDescription as getNodeDescriptionRequest, @@ -36,6 +36,7 @@ import { getMixnodeAvgUptime, getMixnodeRewardEstimation, getGatewayReport, + getMixnodeUptime, } from '../requests'; import { useCheckOwnership } from '../hooks/useCheckOwnership'; import { AppContext } from './main'; @@ -71,10 +72,12 @@ export type TBondedMixnode = { verlocPort: number; version: string; isUnbonding: boolean; + uptime: number; }; export interface TBondedGateway { - name: string; + name?: string; + id: number; identityKey: string; ip: string; bond: DecCoin; @@ -134,7 +137,7 @@ export const BondingContext = createContext<TBondingContext>({ }, }); -export const BondingContextProvider = ({ children }: { children?: React.ReactNode }): JSX.Element => { +export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Element => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState<string>(); const [bondedNode, setBondedNode] = useState<TBondedMixnode | TBondedGateway>(); @@ -154,14 +157,18 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod status: MixnodeStatus; stakeSaturation: string; estimatedRewards?: DecCoin; + uptime: number; } = { status: 'not_found', stakeSaturation: '0', + uptime: 0, }; try { const statusResponse = await getMixnodeStatus(mixId); + const uptime = await getMixnodeUptime(mixId); additionalDetails.status = statusResponse.status; + additionalDetails.uptime = uptime; } catch (e) { Console.log('getMixnodeStatus fails', e); } @@ -259,7 +266,8 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod rewarding_details, bond_information: { mix_id }, } = data; - const { status, stakeSaturation, estimatedRewards } = await getAdditionalMixnodeDetails(mix_id); + + const { status, stakeSaturation, estimatedRewards, uptime } = await getAdditionalMixnodeDetails(mix_id); const setProbabilities = await getSetProbabilities(mix_id); const nodeDescription = await getNodeDescription( bond_information.mix_node.host, @@ -267,6 +275,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod ); const routingScore = await getAvgUptime(); setBondedNode({ + id: data.bond_information.mix_id, name: nodeDescription?.name, mixId: mix_id, identityKey: bond_information.mix_node.identity_key, @@ -279,6 +288,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod delegators: rewarding_details.unique_delegations, proxy: bond_information.proxy, operatorRewards, + uptime, status, stakeSaturation, operatorCost: decCoinToDisplay(rewarding_details.cost_params.interval_operating_cost), diff --git a/nym-wallet/src/context/buy.tsx b/nym-wallet/src/context/buy.tsx index 855f5e988a..4444268591 100644 --- a/nym-wallet/src/context/buy.tsx +++ b/nym-wallet/src/context/buy.tsx @@ -16,7 +16,7 @@ export const BuyContext = createContext<TBuyContext>({ refresh: async () => undefined, }); -export const BuyContextProvider = ({ children }: { children?: React.ReactNode }): JSX.Element => { +export const BuyContextProvider: FCWithChildren = ({ children }): JSX.Element => { const [loading, setLoading] = useState(false); const [error, setError] = useState<string>(); diff --git a/nym-wallet/src/context/delegations.tsx b/nym-wallet/src/context/delegations.tsx index 678b60c305..79ee6380f7 100644 --- a/nym-wallet/src/context/delegations.tsx +++ b/nym-wallet/src/context/delegations.tsx @@ -1,12 +1,12 @@ import React, { createContext, FC, useCallback, useContext, useEffect, useMemo, useState } from 'react'; -import { getDelegationSummary, undelegateAllFromMixnode, undelegateFromMixnode } from 'src/requests/delegation'; +import { getDelegationSummary, undelegateFromMixnode } from 'src/requests/delegation'; import { - DelegationWithEverything, - FeeDetails, DecCoin, + DelegationWithEverything, + Fee, + FeeDetails, TransactionExecuteResult, WrappedDelegationEvent, - Fee, } from '@nymproject/types'; import type { Network } from 'src/types'; import { @@ -64,6 +64,7 @@ export const DelegationContext = createContext<TDelegationContext>({ export const DelegationContextProvider: FC<{ network?: Network; + children: React.ReactNode; // eslint-disable-next-line @typescript-eslint/no-unused-vars }> = ({ network, children }) => { const [isLoading, setIsLoading] = useState(true); diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index f8956f9298..a638a7568d 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -2,7 +2,7 @@ import React, { createContext, useEffect, useMemo, useState } from 'react'; import { forage } from '@tauri-apps/tauri-forage'; import { useNavigate } from 'react-router-dom'; import { useSnackbar } from 'notistack'; -import { Account, AccountEntry, MixNodeBond, MixNodeDetails } from '@nymproject/types'; +import { Account, AccountEntry, MixNodeDetails } from '@nymproject/types'; import { getVersion } from '@tauri-apps/api/app'; import { AppEnv, Network } from '../types'; import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance'; @@ -21,6 +21,7 @@ import { Console } from '../utils/console'; export const urls = (networkName?: Network) => networkName === 'MAINNET' ? { + mixnetExplorer: 'https://mixnet.explorers.guru/', blockExplorer: 'https://blocks.nymtech.net', networkExplorer: 'https://explorer.nymtech.net', } @@ -65,7 +66,7 @@ export type TAppContext = { export const AppContext = createContext({} as TAppContext); -export const AppProvider = ({ children }: { children: React.ReactNode }) => { +export const AppProvider: FCWithChildren = ({ children }) => { const [clientDetails, setClientDetails] = useState<Account>(); const [storedAccounts, setStoredAccounts] = useState<AccountEntry[]>(); const [mixnodeDetails, setMixnodeDetails] = useState<MixNodeDetails | null>(null); diff --git a/nym-wallet/src/context/mocks/accounts.tsx b/nym-wallet/src/context/mocks/accounts.tsx index 7a3ff838e1..4186c8df34 100644 --- a/nym-wallet/src/context/mocks/accounts.tsx +++ b/nym-wallet/src/context/mocks/accounts.tsx @@ -2,7 +2,7 @@ import React, { useMemo, useState } from 'react'; import { AccountEntry } from '@nymproject/types'; import { AccountsContext, TAccountMnemonic, TAccountsDialog } from '../accounts'; -export const MockAccountsProvider: React.FC = ({ children }) => { +export const MockAccountsProvider: FCWithChildren = ({ children }) => { const [accounts, setAccounts] = useState<AccountEntry[]>([{ id: 'Account_1', address: 'abc123' }]); const [selectedAccount, setSelectedAccount] = useState<AccountEntry | undefined>({ id: 'Account_1', diff --git a/nym-wallet/src/context/mocks/bonding.tsx b/nym-wallet/src/context/mocks/bonding.tsx index 95e770850b..610b9ee299 100644 --- a/nym-wallet/src/context/mocks/bonding.tsx +++ b/nym-wallet/src/context/mocks/bonding.tsx @@ -1,14 +1,14 @@ -import { FeeDetails, DecCoin, TransactionExecuteResult } from '@nymproject/types'; +import { FeeDetails, TransactionExecuteResult } from '@nymproject/types'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import type { Network } from 'src/types'; -import { TBondedGateway, TBondedMixnode, BondingContext } from '../bonding'; +import { BondingContext, TBondedGateway, TBondedMixnode } from '../bonding'; import { mockSleep } from './utils'; const SLEEP_MS = 1000; const bondedMixnodeMock: TBondedMixnode = { - name: 'Monster node', mixId: 1, + name: 'Monster node', identityKey: '7mjM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F', stake: { denom: 'nym', amount: '1234' }, bond: { denom: 'nym', amount: '1234' }, @@ -28,9 +28,11 @@ const bondedMixnodeMock: TBondedMixnode = { verlocPort: 1790, version: '1.0.2', isUnbonding: false, + uptime: 1, }; const bondedGatewayMock: TBondedGateway = { + id: 1, name: 'Monster node', identityKey: 'WayM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F', ip: '112.43.234.57', diff --git a/nym-wallet/src/context/mocks/buy.tsx b/nym-wallet/src/context/mocks/buy.tsx index 0d7bca418d..934b9f54d9 100644 --- a/nym-wallet/src/context/mocks/buy.tsx +++ b/nym-wallet/src/context/mocks/buy.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { BuyContext } from '../buy'; import { mockSleep } from './utils'; -export const MockBuyContextProvider = ({ children }: { children?: React.ReactNode }): JSX.Element => { +export const MockBuyContextProvider: FCWithChildren = ({ children }): JSX.Element => { const [loading, setLoading] = useState(false); const [error, setError] = useState<string>(); diff --git a/nym-wallet/src/context/mocks/delegations.tsx b/nym-wallet/src/context/mocks/delegations.tsx index bc216fd7bf..0c42a2b2e4 100644 --- a/nym-wallet/src/context/mocks/delegations.tsx +++ b/nym-wallet/src/context/mocks/delegations.tsx @@ -1,11 +1,11 @@ -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { - DelegationWithEverything, - DecCoin, - TransactionExecuteResult, - FeeDetails, - Fee, CurrencyDenom, + DecCoin, + DelegationWithEverything, + Fee, + FeeDetails, + TransactionExecuteResult, } from '@nymproject/types'; import { DelegationContext, TDelegationTransaction } from '../delegations'; @@ -63,7 +63,7 @@ let mockDelegations: DelegationWithEverything[] = [ }, ]; -export const MockDelegationContextProvider: FC<{}> = ({ children }) => { +export const MockDelegationContextProvider: FCWithChildren = ({ children }) => { const [trigger, setTrigger] = useState<Date>(new Date()); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<string>(); @@ -190,18 +190,17 @@ export const MockDelegationContextProvider: FC<{}> = ({ children }) => { }; }; - const undelegateVesting = async (mix_id: number, _fee?: FeeDetails) => { - return { - logs_json: '', - data_json: '', - transaction_hash: '', - gas_info: { - gas_wanted: { gas_units: BigInt(1) }, - gas_used: { gas_units: BigInt(1) }, - }, - fee: { amount: '1', denom: 'nym' as CurrencyDenom }, - }; - }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const undelegateVesting = async (mix_id: number, _fee?: FeeDetails) => ({ + logs_json: '', + data_json: '', + transaction_hash: '', + gas_info: { + gas_wanted: { gas_units: BigInt(1) }, + gas_used: { gas_units: BigInt(1) }, + }, + fee: { amount: '1', denom: 'nym' as CurrencyDenom }, + }); const resetState = () => { setIsLoading(true); diff --git a/nym-wallet/src/context/mocks/main.tsx b/nym-wallet/src/context/mocks/main.tsx index c9f32dedd8..ff49784f7b 100644 --- a/nym-wallet/src/context/mocks/main.tsx +++ b/nym-wallet/src/context/mocks/main.tsx @@ -1,8 +1,8 @@ -import React, { FC, useMemo } from 'react'; +import React, { useMemo } from 'react'; import type { TAppContext } from '../main'; import { AppContext } from '../main'; -export const MockMainContextProvider: FC<{}> = ({ children }) => { +export const MockMainContextProvider: FCWithChildren = ({ children }) => { const memoizedValue = useMemo<TAppContext>( () => ({ mode: 'light', diff --git a/nym-wallet/src/context/mocks/rewards.tsx b/nym-wallet/src/context/mocks/rewards.tsx index 01730c40af..c89fdb00b3 100644 --- a/nym-wallet/src/context/mocks/rewards.tsx +++ b/nym-wallet/src/context/mocks/rewards.tsx @@ -1,10 +1,10 @@ -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { DelegationWithEverything, TransactionExecuteResult } from '@nymproject/types'; import { RewardsContext, TRewardsTransaction } from '../rewards'; import { useDelegationContext } from '../delegations'; import { mockSleep } from './utils'; -export const MockRewardsContextProvider: FC = ({ children }) => { +export const MockRewardsContextProvider: FCWithChildren = ({ children }) => { const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<string>(); const [totalRewards, setTotalRewards] = useState<undefined | string>(); diff --git a/nym-wallet/src/context/rewards.tsx b/nym-wallet/src/context/rewards.tsx index 2636849bb3..dc6b72a44f 100644 --- a/nym-wallet/src/context/rewards.tsx +++ b/nym-wallet/src/context/rewards.tsx @@ -1,4 +1,4 @@ -import React, { createContext, FC, useContext, useEffect, useMemo, useState } from 'react'; +import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { FeeDetails, TransactionExecuteResult } from '@nymproject/types'; import { useDelegationContext } from './delegations'; import { claimDelegatorRewards } from '../requests'; @@ -24,7 +24,7 @@ export const RewardsContext = createContext<TRewardsContext>({ }, }); -export const RewardsContextProvider: FC<{}> = ({ children }) => { +export const RewardsContextProvider: FCWithChildren = ({ children }) => { const { isLoading, totalRewards, refresh } = useDelegationContext(); const [error, setError] = useState<string>(); diff --git a/nym-wallet/src/hooks/useGetBalance.ts b/nym-wallet/src/hooks/useGetBalance.ts index 0da3ca7476..240910e493 100644 --- a/nym-wallet/src/hooks/useGetBalance.ts +++ b/nym-wallet/src/hooks/useGetBalance.ts @@ -9,11 +9,19 @@ import { getOriginalVesting, getCurrentVestingPeriod, getVestingAccountInfo, + getSpendableRewardCoins, + getSpendableVestedCoins, } from '../requests'; import { Console } from '../utils/console'; type TTokenAllocation = { - [key in 'vesting' | 'vested' | 'locked' | 'spendable']: DecCoin['amount']; + [key in + | 'vesting' + | 'vested' + | 'locked' + | 'spendable' + | 'spendableRewardCoins' + | 'spendableVestedCoins']: DecCoin['amount']; }; export type TUseuserBalance = { @@ -54,6 +62,8 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => { vestedCoins, lockedCoins, spendableCoins, + spendableVestedCoins, + spendableRewardCoins, currentPeriod, vestingAccountDetail, ] = await Promise.all([ @@ -62,6 +72,8 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => { getVestedCoins(clientDetails?.client_address), getLockedCoins(), getSpendableCoins(), + getSpendableVestedCoins(), + getSpendableRewardCoins(), getCurrentVestingPeriod(clientDetails?.client_address), getVestingAccountInfo(clientDetails?.client_address), ]); @@ -72,6 +84,8 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => { vested: vestedCoins.amount, locked: lockedCoins.amount, spendable: spendableCoins.amount, + spendableVestedCoins: spendableVestedCoins.amount, + spendableRewardCoins: spendableRewardCoins.amount, }); setVestingAccountInfo(vestingAccountDetail); } catch (e) { diff --git a/nym-wallet/src/hooks/useGetFee.ts b/nym-wallet/src/hooks/useGetFee.ts index a38179c7f4..a563549182 100644 --- a/nym-wallet/src/hooks/useGetFee.ts +++ b/nym-wallet/src/hooks/useGetFee.ts @@ -1,6 +1,7 @@ -import { FeeDetails } from '@nymproject/types'; +import { DecCoin, FeeDetails } from '@nymproject/types'; import { useState } from 'react'; import { Console } from 'src/utils/console'; +import { getCustomFees } from '../requests'; export function useGetFee() { const [fee, setFee] = useState<FeeDetails>(); @@ -19,6 +20,18 @@ export function useGetFee() { setIsFeeLoading(false); } + async function setFeeManually(amount: DecCoin) { + try { + setIsFeeLoading(true); + const fees = await getCustomFees({ feesAmount: amount }); + setFee(fees); + } catch (e) { + Console.error(e); + setFeeError(e as string); + } + setIsFeeLoading(false); + } + const resetFeeState = () => { setFee(undefined); setIsFeeLoading(false); @@ -30,6 +43,7 @@ export function useGetFee() { isFeeLoading, feeError, getFee, + setFeeManually, resetFeeState, }; } diff --git a/nym-wallet/src/index.tsx b/nym-wallet/src/index.tsx index b585c8c3d3..b1b5a1053c 100644 --- a/nym-wallet/src/index.tsx +++ b/nym-wallet/src/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { ErrorBoundary } from 'react-error-boundary'; import { BrowserRouter as Router } from 'react-router-dom'; import { SnackbarProvider } from 'notistack'; @@ -38,6 +38,9 @@ const App = () => { ); }; -const root = document.getElementById('root'); +const elem = document.getElementById('root'); -ReactDOM.render(<App />, root); +if (elem) { + const root = createRoot(elem); + root.render(<App />); +} diff --git a/nym-wallet/src/layouts/AppLayout.tsx b/nym-wallet/src/layouts/AppLayout.tsx index 807c369801..64b186b445 100644 --- a/nym-wallet/src/layouts/AppLayout.tsx +++ b/nym-wallet/src/layouts/AppLayout.tsx @@ -5,7 +5,7 @@ import { useTheme } from '@mui/material/styles'; import { AppContext } from 'src/context'; import { AppBar, LoadingPage, Nav } from '../components'; -export const ApplicationLayout: React.FC = ({ children }) => { +export const ApplicationLayout: FCWithChildren = ({ children }) => { const theme = useTheme(); const { isLoading, appVersion } = useContext(AppContext); diff --git a/nym-wallet/src/layouts/AuthLayout.tsx b/nym-wallet/src/layouts/AuthLayout.tsx index 0f182301e4..6e951b136e 100644 --- a/nym-wallet/src/layouts/AuthLayout.tsx +++ b/nym-wallet/src/layouts/AuthLayout.tsx @@ -5,7 +5,7 @@ import { AppContext } from 'src/context'; import { LoadingPage } from 'src/components'; import { Step } from '../pages/auth/components/step'; -export const AuthLayout: React.FC = ({ children }) => { +export const AuthLayout: FCWithChildren = ({ children }) => { const { isLoading } = useContext(AppContext); return isLoading ? ( diff --git a/nym-wallet/src/layouts/PageLayout.tsx b/nym-wallet/src/layouts/PageLayout.tsx index 9f55d3da3c..3ab5d5f995 100644 --- a/nym-wallet/src/layouts/PageLayout.tsx +++ b/nym-wallet/src/layouts/PageLayout.tsx @@ -1,7 +1,10 @@ import React from 'react'; import { Box } from '@mui/material'; -export const PageLayout: React.FC<{ position?: 'flex-start' | 'flex-end' }> = ({ position, children }) => ( +export const PageLayout: FCWithChildren<{ position?: 'flex-start' | 'flex-end'; children: React.ReactNode }> = ({ + position, + children, +}) => ( <Box sx={{ maxHeight: 'calc(100% - 65px)', diff --git a/nym-wallet/src/pages/Admin/index.tsx b/nym-wallet/src/pages/Admin/index.tsx index b3a8c157c5..978360f254 100644 --- a/nym-wallet/src/pages/Admin/index.tsx +++ b/nym-wallet/src/pages/Admin/index.tsx @@ -7,7 +7,7 @@ import { getContractParams, setContractParams } from '../../requests'; import { PageLayout } from '../../layouts'; import { AppContext } from '../../context'; -const AdminForm: React.FC<{ +const AdminForm: FCWithChildren<{ params: TauriContractStateParams; }> = ({ params }) => { const { @@ -80,7 +80,7 @@ const AdminForm: React.FC<{ ); }; -export const Admin: React.FC = () => { +export const Admin: FCWithChildren = () => { const { isAdminAddress, network } = useContext(AppContext); const [isLoading, setIsLoading] = useState(false); const [params, setParams] = useState<TauriContractStateParams>(); diff --git a/nym-wallet/src/pages/auth/components/password-strength.tsx b/nym-wallet/src/pages/auth/components/password-strength.tsx index 7e867f73fd..87ad9a3aa9 100644 --- a/nym-wallet/src/pages/auth/components/password-strength.tsx +++ b/nym-wallet/src/pages/auth/components/password-strength.tsx @@ -1,52 +1,61 @@ /* eslint-disable no-nested-ternary */ -import React, { useEffect, useState } from 'react'; +import React from 'react'; +import zxcvbn, { ZXCVBNScore } from 'zxcvbn'; import { LockOutlined } from '@mui/icons-material'; import { LinearProgress, Stack, Typography, Box } from '@mui/material'; -type TStrength = 'weak' | 'medium' | 'strong' | 'init'; - -const strong = /^(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/; -const medium = /^(((?=.*[a-z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[0-9])))(?=.{6,})/; - const colorMap = { - init: 'inherit' as 'inherit', - weak: 'error' as 'error', - medium: 'warning' as 'warning', - strong: 'success' as 'success', + 4: 'success' as 'success', + 3: 'success' as 'success', + 2: 'warning' as 'warning', + 1: 'error' as 'error', + 0: 'error' as 'error', }; -const getText = (strength: TStrength) => { - switch (strength) { - case 'strong': +const getText = (score: ZXCVBNScore) => { + switch (score) { + case 4: + return 'Very strong password'; + case 3: return 'Strong password'; - case 'medium': - return 'Medium strength password'; - case 'weak': + case 2: + return 'Average password'; + case 1: return 'Weak password'; + case 0: + return 'Very weak password'; default: - return 'Password strength'; + return ''; } }; -const getTextColor = (strength: TStrength) => { - switch (strength) { - case 'strong': +const getColor = (score: ZXCVBNScore) => { + switch (score) { + case 4: return 'success.main'; - case 'medium': + case 3: + return 'success.main'; + case 2: return 'warning.main'; - case 'weak': + case 1: + return 'error.main'; + case 0: return 'error.main'; default: return 'grey.500'; } }; -const getPasswordStrength = (strength: TStrength) => { - switch (strength) { - case 'strong': +const getPasswordStrength = (score: ZXCVBNScore) => { + switch (score) { + case 4: return 100; - case 'medium': + case 3: + return 75; + case 2: return 50; + case 1: + return 25; default: return 0; } @@ -54,47 +63,32 @@ const getPasswordStrength = (strength: TStrength) => { export const PasswordStrength = ({ password, - onChange, + withWarnings, + handleIsSafePassword, }: { password: string; - onChange: (isStrong: boolean) => void; + withWarnings?: boolean; + handleIsSafePassword: (isSafe: boolean) => void; }) => { - const [strength, setStrength] = useState<TStrength>('init'); + const result = zxcvbn(password); - useEffect(() => { - if (password.length === 0) { - setStrength('init'); - return; - } + handleIsSafePassword(result.score > 1); - if (password.match(strong)) { - setStrength('strong'); - return; - } - - if (password.match(medium)) { - setStrength('medium'); - return; - } - setStrength('weak'); - }, [password]); - - useEffect(() => { - if (strength === 'strong') { - onChange(true); - } else { - onChange(false); - } - }, [strength]); + if (!password.length) return null; return ( <Stack spacing={0.5}> - <LinearProgress variant="determinate" color={colorMap[strength]} value={getPasswordStrength(strength)} /> - <Box display="flex" alignItems="center"> - <LockOutlined sx={{ fontSize: 15, color: getTextColor(strength) }} /> - <Typography variant="caption" sx={{ ml: 0.5, color: getTextColor(strength) }}> - {getText(strength)} - </Typography> + <LinearProgress variant="determinate" color={colorMap[result.score]} value={getPasswordStrength(result.score)} /> + <Box display="flex" alignItems="center" justifyContent="space-between"> + <Box display="flex" alignItems="center"> + <LockOutlined sx={{ fontSize: 15, color: getColor(result.score) }} /> + <Typography variant="caption" sx={{ ml: 0.5, color: getColor(result.score) }}> + {getText(result.score)} + </Typography> + </Box> + {withWarnings && result.feedback.warning && ( + <Typography variant="caption">{result.feedback.warning}</Typography> + )} </Box> </Stack> ); diff --git a/nym-wallet/src/pages/auth/components/passwordStrength.stories.tsx b/nym-wallet/src/pages/auth/components/passwordStrength.stories.tsx new file mode 100644 index 0000000000..3746b2015a --- /dev/null +++ b/nym-wallet/src/pages/auth/components/passwordStrength.stories.tsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Stack, TextField } from '@mui/material'; +import { PasswordStrength } from './password-strength'; + +export default { + title: 'Wallet / Password Strength', + component: PasswordStrength, +} as ComponentMeta<typeof PasswordStrength>; + +const Template: ComponentStory<typeof PasswordStrength> = ({ password, withWarnings, handleIsSafePassword }) => { + const [value, setValue] = React.useState(password); + return ( + <Stack alignContent="center"> + <TextField value={value} onChange={(e) => setValue(e.target.value)} sx={{ mb: 0.5 }} /> + {!!password.length && ( + <PasswordStrength handleIsSafePassword={handleIsSafePassword} withWarnings={withWarnings} password={password} /> + )} + </Stack> + ); +}; + +export const VeryStrong = Template.bind({}); +VeryStrong.args = { password: 'fedgklnrf34£', withWarnings: true, handleIsSafePassword: () => undefined }; + +export const Strong = Template.bind({}); +Strong.args = { password: '"56%abc123?@', withWarnings: true, handleIsSafePassword: () => undefined }; + +export const Average = Template.bind({}); +Average.args = { password: '"abc123?', withWarnings: true, handleIsSafePassword: () => undefined }; + +export const Weak = Template.bind({}); +Weak.args = { password: 'abc123?', withWarnings: true, handleIsSafePassword: () => undefined }; + +export const VeryWeak = Template.bind({}); +VeryWeak.args = { + password: 'abc123', + withWarnings: true, + handleIsSafePassword: () => undefined, +}; + +export const WithName = Template.bind({}); +WithName.args = { + password: 'fred', + withWarnings: true, + handleIsSafePassword: () => undefined, +}; + +export const WithSequence = Template.bind({}); +WithSequence.args = { + password: '121212', + withWarnings: true, + handleIsSafePassword: () => undefined, +}; + +export const Default = Template.bind({}); +Default.args = { + password: 'abc123', + withWarnings: true, + handleIsSafePassword: () => undefined, +}; diff --git a/nym-wallet/src/pages/auth/index.tsx b/nym-wallet/src/pages/auth/index.tsx deleted file mode 100644 index 598566db6f..0000000000 --- a/nym-wallet/src/pages/auth/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { AuthProvider } from 'src/context'; -import { AuthRoutes } from 'src/routes/auth'; - -export const Auth = () => ( - <AuthProvider> - <AuthRoutes /> - </AuthProvider> -); diff --git a/nym-wallet/src/pages/auth/pages/connect-password.tsx b/nym-wallet/src/pages/auth/pages/connect-password.tsx index 133d0a27cb..476f0a9aca 100644 --- a/nym-wallet/src/pages/auth/pages/connect-password.tsx +++ b/nym-wallet/src/pages/auth/pages/connect-password.tsx @@ -9,9 +9,8 @@ import { Subtitle, Title, PasswordStrength } from '../components'; export const ConnectPassword = () => { const [confirmedPassword, setConfirmedPassword] = useState<string>(''); - const [isStrongPassword, setIsStrongPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); - + const [isSafePassword, setIsSafePassword] = useState(false); const { mnemonic, password, setPassword, resetState } = useContext(AuthContext); const navigate = useNavigate(); @@ -49,7 +48,7 @@ export const ConnectPassword = () => { label="Password" autoFocus /> - <PasswordStrength password={password} onChange={(isStrong) => setIsStrongPassword(isStrong)} /> + <PasswordStrength password={password} handleIsSafePassword={setIsSafePassword} withWarnings /> </> <PasswordInput password={confirmedPassword} @@ -59,7 +58,7 @@ export const ConnectPassword = () => { <Button size="large" variant="contained" - disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading} + disabled={password !== confirmedPassword || password.length === 0 || isLoading || !isSafePassword} onClick={storePassword} > {isLoading ? <CircularProgress size={25} /> : 'Create password'} diff --git a/nym-wallet/src/pages/auth/pages/create-mnemonic.tsx b/nym-wallet/src/pages/auth/pages/create-mnemonic.tsx index 2809d3b3b1..e3ab373d55 100644 --- a/nym-wallet/src/pages/auth/pages/create-mnemonic.tsx +++ b/nym-wallet/src/pages/auth/pages/create-mnemonic.tsx @@ -1,13 +1,13 @@ -import React, { useContext, useEffect } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Container, Button, Stack } from '@mui/material'; import { AuthContext } from 'src/context/auth'; -import { useClipboard } from 'use-clipboard-copy'; import { Mnemonic } from '../../../components'; export const CreateMnemonic = () => { const { mnemonic, mnemonicWords, generateMnemonic, resetState } = useContext(AuthContext); const navigate = useNavigate(); + const [confirmed, setConfirmed] = useState(false); useEffect(() => { if (mnemonicWords.length === 0) { @@ -15,12 +15,10 @@ export const CreateMnemonic = () => { } }, []); - const { copy, copied } = useClipboard({ copiedTimeout: 5000 }); return ( - <Container maxWidth="xs"> + <Container maxWidth="sm"> <Stack alignItems="center" spacing={3} maxWidth="xs"> - <Mnemonic mnemonic={mnemonic} handleCopy={copy} copied={copied} /> - + <Mnemonic mnemonic={mnemonic} handleConfirmed={setConfirmed} confirmed={confirmed} withTitle /> <Button variant="contained" color="primary" @@ -28,9 +26,9 @@ export const CreateMnemonic = () => { size="large" onClick={() => navigate('/verify-mnemonic')} sx={{ width: '100%', fontSize: 15 }} - disabled={!copied} + disabled={!confirmed} > - I saved my mnemonic + Continue </Button> <Button onClick={() => { diff --git a/nym-wallet/src/pages/auth/pages/create-password.tsx b/nym-wallet/src/pages/auth/pages/create-password.tsx index cf894f20c4..e071c6dcea 100644 --- a/nym-wallet/src/pages/auth/pages/create-password.tsx +++ b/nym-wallet/src/pages/auth/pages/create-password.tsx @@ -10,8 +10,8 @@ import { Subtitle, Title, PasswordStrength } from '../components'; export const CreatePassword = () => { const { password, setPassword, resetState, mnemonic } = useContext(AuthContext); const [confirmedPassword, setConfirmedPassword] = useState<string>(''); - const [isStrongPassword, setIsStrongPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [isSafePassword, setIsSafePassword] = useState(false); const navigate = useNavigate(); @@ -48,7 +48,7 @@ export const CreatePassword = () => { label="Password" autoFocus /> - <PasswordStrength password={password} onChange={(isStrong) => setIsStrongPassword(isStrong)} /> + <PasswordStrength password={password} handleIsSafePassword={setIsSafePassword} withWarnings /> </> <PasswordInput password={confirmedPassword} @@ -58,7 +58,7 @@ export const CreatePassword = () => { <Button size="large" variant="contained" - disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading} + disabled={password !== confirmedPassword || password.length === 0 || isLoading || !isSafePassword} onClick={storePassword} > Next diff --git a/nym-wallet/src/pages/auth/pages/welcome.tsx b/nym-wallet/src/pages/auth/pages/welcome.tsx index 9f48e47058..e5bb19a264 100644 --- a/nym-wallet/src/pages/auth/pages/welcome.tsx +++ b/nym-wallet/src/pages/auth/pages/welcome.tsx @@ -4,7 +4,7 @@ import { Button, Stack } from '@mui/material'; import { useNavigate } from 'react-router-dom'; import { SubtitleSlick, Title } from '../components'; -export const WelcomeContent: React.FC<{}> = () => { +export const WelcomeContent: FCWithChildren<{}> = () => { const navigate = useNavigate(); return ( diff --git a/nym-wallet/src/pages/balance/Balance.tsx b/nym-wallet/src/pages/balance/Balance.tsx new file mode 100644 index 0000000000..1125076ee5 --- /dev/null +++ b/nym-wallet/src/pages/balance/Balance.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Alert, Grid, Typography } from '@mui/material'; +import { Link } from '@nymproject/react/link/Link'; +import { Network } from 'src/types'; +import { Balance } from '@nymproject/types'; +import { NymCard, ClientAddress } from '../../components'; +import { urls } from '../../context/main'; + +export const BalanceCard = ({ + userBalance, + userBalanceError, + network, + clientAddress, +}: { + userBalance?: Balance; + userBalanceError?: string; + network?: Network; + clientAddress?: string; +}) => ( + <NymCard title="Balance" data-testid="check-balance" borderless Action={<ClientAddress withCopy showEntireAddress />}> + <Grid container direction="column" spacing={2}> + <Grid item> + {userBalanceError && ( + <Alert severity="error" data-testid="error-refresh" sx={{ p: 2 }}> + {userBalanceError} + </Alert> + )} + {!userBalanceError && ( + <Typography + data-testid="refresh-success" + sx={{ + color: 'text.primary', + textTransform: 'uppercase', + fontWeight: '600', + fontSize: 28, + }} + variant="h5" + > + {userBalance?.printable_balance} + </Typography> + )} + </Grid> + {network && ( + <Grid item> + <Link + href={`${urls(network).mixnetExplorer}/account/${clientAddress}`} + target="_blank" + text="Last transactions" + fontSize={14} + /> + </Grid> + )} + </Grid> + </NymCard> +); diff --git a/nym-wallet/src/pages/balance/Vesting.tsx b/nym-wallet/src/pages/balance/Vesting.tsx new file mode 100644 index 0000000000..23e018438c --- /dev/null +++ b/nym-wallet/src/pages/balance/Vesting.tsx @@ -0,0 +1,77 @@ +import React, { useEffect } from 'react'; +import { Refresh } from '@mui/icons-material'; +import { Grid, IconButton, Typography } from '@mui/material'; +import { useSnackbar } from 'notistack'; +import { NymCard } from 'src/components'; +import { TokenTransfer } from 'src/components/Balance/cards/TokenTransfer'; +import { OriginalVestingResponse } from '@nymproject/types'; +import { VestingSchedule } from 'src/components/Balance/cards/VestingSchedule'; + +export const VestingCard = ({ + unlockedTokens, + unlockedRewards, + unlockedTransferable, + originalVesting, + onTransfer, + fetchBalance, + fetchTokenAllocation, +}: { + unlockedTokens?: string; + unlockedRewards?: string; + unlockedTransferable?: string; + originalVesting?: OriginalVestingResponse; + fetchTokenAllocation: () => Promise<void>; + fetchBalance: () => Promise<void>; + onTransfer: () => Promise<void>; +}) => { + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + + const refreshBalances = async () => { + await fetchBalance(); + await fetchTokenAllocation(); + }; + + useEffect(() => { + closeSnackbar(); + fetchTokenAllocation(); + }, []); + + if (!originalVesting) return null; + + return ( + <NymCard + title="Vesting Schedule" + subheader={ + <Typography variant="caption" sx={{ color: 'nym.text.muted' }}> + You can use up to 10% of your locked tokens for bonding and delegating + </Typography> + } + borderless + data-testid="check-unvested-tokens" + Action={ + <IconButton + onClick={async () => { + await refreshBalances(); + enqueueSnackbar('Balances updated', { variant: 'success', preventDuplicate: true }); + }} + > + <Refresh /> + </IconButton> + } + > + <Grid container spacing={3}> + <Grid item xs={12} xl={8}> + <VestingSchedule /> + </Grid> + <Grid item xs={12} xl={4}> + <TokenTransfer + onTransfer={onTransfer} + unlockedTokens={unlockedTokens} + unlockedRewards={unlockedRewards} + unlockedTransferable={unlockedTransferable} + /> + </Grid> + </Grid> + </NymCard> + ); +}; diff --git a/nym-wallet/src/pages/balance/balance.tsx b/nym-wallet/src/pages/balance/balance.tsx deleted file mode 100644 index 82a5fec102..0000000000 --- a/nym-wallet/src/pages/balance/balance.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useContext, useEffect } from 'react'; -import { Alert, Grid, Typography } from '@mui/material'; -import { Link } from '@nymproject/react/link/Link'; -import { NymCard, ClientAddress } from '../../components'; -import { AppContext, urls } from '../../context/main'; - -export const BalanceCard = () => { - const { userBalance, clientDetails, network } = useContext(AppContext); - - useEffect(() => { - userBalance.fetchBalance(); - }, []); - - return ( - <NymCard - title="Balance" - data-testid="check-balance" - borderless - Action={<ClientAddress withCopy showEntireAddress />} - > - <Grid container direction="column" spacing={2}> - <Grid item> - {userBalance.error && ( - <Alert severity="error" data-testid="error-refresh" sx={{ p: 2 }}> - {userBalance.error} - </Alert> - )} - {!userBalance.error && ( - <Typography - data-testid="refresh-success" - sx={{ - color: 'text.primary', - textTransform: 'uppercase', - fontWeight: '600', - fontSize: 28, - }} - variant="h5" - > - {userBalance.balance?.printable_balance} - </Typography> - )} - </Grid> - {network && ( - <Grid item> - <Link - href={`${urls(network).blockExplorer}/account/${clientDetails?.client_address}`} - target="_blank" - text="Last transactions" - fontSize={14} - /> - </Grid> - )} - </Grid> - </NymCard> - ); -}; diff --git a/nym-wallet/src/pages/balance/components/vesting-timeline.tsx b/nym-wallet/src/pages/balance/components/vesting-timeline.tsx deleted file mode 100644 index 5b5230604e..0000000000 --- a/nym-wallet/src/pages/balance/components/vesting-timeline.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-disable react/no-array-index-key */ -import React, { useContext } from 'react'; -import { useTheme } from '@mui/material/styles'; -import { Box, Tooltip, Typography } from '@mui/material'; -import { format } from 'date-fns'; -import { AppContext } from '../../../context'; - -const calculateMarkerPosition = (arrLength: number, index: number) => (1 / arrLength) * 100 * index; - -const Marker: React.FC<{ tooltipText: string; color: string; position: string }> = ({ - tooltipText, - color, - position, -}) => ( - <Tooltip title={tooltipText}> - <rect x={position} width="4" height="12" rx="1" fill={color} style={{ cursor: 'pointer' }} /> - </Tooltip> -); - -export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ percentageComplete }) => { - const { - userBalance: { currentVestingPeriod, vestingAccountInfo }, - } = useContext(AppContext); - - const theme = useTheme(); - - const nextPeriod = - typeof currentVestingPeriod === 'object' && !!vestingAccountInfo?.periods - ? Number(vestingAccountInfo?.periods[currentVestingPeriod.In + 1]?.start_time) - : undefined; - - return ( - <Box display="flex" flexDirection="column" gap={1} position="relative" width="100%"> - <svg width="100%" height="12"> - <rect y="2" width="100%" height="6" rx="0" fill="#E6E6E6" /> - <rect y="2" width={`${percentageComplete}%`} height="6" rx="0" fill={theme.palette.success.main} /> - {vestingAccountInfo?.periods.map((period, i, arr) => ( - <Marker - position={`${calculateMarkerPosition(arr.length, i)}%`} - color={ - Math.ceil(+percentageComplete) >= calculateMarkerPosition(arr.length, i) - ? theme.palette.success.main - : '#B9B9B9' - } - tooltipText={format(new Date(Number(period.start_time) * 1000), 'HH:mm do MMM yyyy')} - key={i} - /> - ))} - <Marker - position="calc(100% - 4px)" - color={percentageComplete === 100 ? theme.palette.success.main : '#B9B9B9'} - tooltipText="End of vesting schedule" - /> - </svg> - {!!nextPeriod && ( - <Typography variant="caption" sx={{ color: 'nym.text.muted', position: 'absolute', top: 15, left: 0 }}> - Next vesting period: {format(new Date(nextPeriod * 1000), 'HH:mm do MMM yyyy')} - </Typography> - )} - </Box> - ); -}; diff --git a/nym-wallet/src/pages/balance/index.tsx b/nym-wallet/src/pages/balance/index.tsx index a788a1c460..89767f370e 100644 --- a/nym-wallet/src/pages/balance/index.tsx +++ b/nym-wallet/src/pages/balance/index.tsx @@ -2,26 +2,45 @@ import React, { useContext, useEffect, useState } from 'react'; import { Box } from '@mui/material'; import { AppContext } from '../../context/main'; -import { BalanceCard } from './balance'; -import { VestingCard } from './vesting'; +import { BalanceCard } from './Balance'; +import { VestingCard } from './Vesting'; import { PageLayout } from '../../layouts'; -import { TransferModal } from './components/TransferModal'; +import { TransferModal } from '../../components/Balance/modals/TransferModal'; export const Balance = () => { const [showTransferModal, setShowTransferModal] = useState(false); - const { userBalance } = useContext(AppContext); + const { userBalance, clientDetails, network } = useContext(AppContext); + + useEffect(() => { + userBalance.fetchBalance(); + }, []); const handleShowTransferModal = async () => { await userBalance.refreshBalances(); setShowTransferModal(true); }; + const appendDenom = (value: string = '') => `${value} ${clientDetails?.display_mix_denom.toUpperCase()}`; + return ( <PageLayout> <Box display="flex" flexDirection="column" gap={4}> - <BalanceCard /> - <VestingCard onTransfer={handleShowTransferModal} /> + <BalanceCard + userBalance={userBalance.balance} + userBalanceError={userBalance.error} + clientAddress={clientDetails?.client_address} + network={network} + /> + <VestingCard + unlockedTokens={appendDenom(userBalance.tokenAllocation?.spendableVestedCoins)} + unlockedRewards={appendDenom(userBalance.tokenAllocation?.spendableRewardCoins)} + unlockedTransferable={appendDenom(userBalance.tokenAllocation?.spendable)} + originalVesting={userBalance.originalVesting} + onTransfer={handleShowTransferModal} + fetchBalance={userBalance.fetchBalance} + fetchTokenAllocation={userBalance.fetchTokenAllocation} + /> {showTransferModal && <TransferModal onClose={() => setShowTransferModal(false)} />} </Box> </PageLayout> diff --git a/nym-wallet/src/pages/balance/types.ts b/nym-wallet/src/pages/balance/types.ts index 5f88fe60b1..4c4ee67403 100644 --- a/nym-wallet/src/pages/balance/types.ts +++ b/nym-wallet/src/pages/balance/types.ts @@ -1,2 +1 @@ export type TResponseState = 'loading' | 'success' | 'fail'; -export type TTransactionDetails = { amount: string; url: string }; diff --git a/nym-wallet/src/pages/balance/vesting.tsx b/nym-wallet/src/pages/balance/vesting.tsx deleted file mode 100644 index 1ef8e0f30e..0000000000 --- a/nym-wallet/src/pages/balance/vesting.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import React from 'react'; -import { Refresh } from '@mui/icons-material'; -import { - Box, - Button, - IconButton, - Table, - TableBody, - TableCell, - TableCellProps, - TableContainer, - TableHead, - TableRow, - Typography, -} from '@mui/material'; -import { useSnackbar } from 'notistack'; -import { useContext, useEffect, useState } from 'react'; -import { NymCard } from 'src/components'; -import { AppContext } from 'src/context/main'; -import { Period } from 'src/types'; -import { VestingTimeline } from './components/vesting-timeline'; - -const columnsHeaders: Array<{ title: string; align: TableCellProps['align'] }> = [ - { title: 'Locked', align: 'left' }, - { title: 'Period', align: 'left' }, - { title: 'Percentage Vested', align: 'left' }, - { title: 'Unlocked', align: 'right' }, -]; - -const vestingPeriod = (current?: Period, original?: number) => { - if (current === 'After') return 'Complete'; - - if (typeof current === 'object' && typeof original === 'number') return `${current.In + 1}/${original}`; - - return 'N/A'; -}; - -const VestingSchedule = () => { - const { userBalance, clientDetails } = useContext(AppContext); - const [vestedPercentage, setVestedPercentage] = useState(0); - - const calculatePercentage = () => { - const { tokenAllocation, originalVesting } = userBalance; - if (tokenAllocation?.vesting && tokenAllocation.vested && tokenAllocation.vested !== '0' && originalVesting) { - const percentage = (+tokenAllocation.vested / +originalVesting.amount.amount) * 100; - const rounded = percentage.toFixed(2); - setVestedPercentage(+rounded); - } else { - setVestedPercentage(0); - } - }; - - useEffect(() => { - calculatePercentage(); - }, [userBalance.tokenAllocation, calculatePercentage]); - - return ( - <TableContainer sx={{ py: 1 }}> - <Table> - <TableHead> - <TableRow> - {columnsHeaders.map((header) => ( - <TableCell key={header.title} sx={{ color: (t) => t.palette.nym.text.muted }} align={header.align}> - {header.title} - </TableCell> - ))} - </TableRow> - </TableHead> - <TableBody> - <TableRow> - <TableCell - sx={{ - color: 'text.primary', - borderBottom: 'none', - textTransform: 'uppercase', - }} - > - {userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} - {clientDetails?.display_mix_denom.toUpperCase()} - </TableCell> - <TableCell - align="left" - sx={{ - color: 'text.primary', - borderBottom: 'none', - }} - > - {vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)} - </TableCell> - <TableCell - sx={{ - color: 'text.primary', - borderBottom: 'none', - }} - > - <Box display="flex" alignItems="center" gap={1}> - <Typography variant="body2">{`${vestedPercentage}%`}</Typography> - <VestingTimeline percentageComplete={vestedPercentage} /> - </Box> - </TableCell> - <TableCell - sx={{ - color: 'text.primary', - borderBottom: 'none', - textTransform: 'uppercase', - }} - align="right" - > - {userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} - {clientDetails?.display_mix_denom.toUpperCase()} - </TableCell> - </TableRow> - </TableBody> - </Table> - </TableContainer> - ); -}; - -const TokenTransfer = () => { - const { userBalance, clientDetails } = useContext(AppContext); - - return ( - <Box sx={{ my: 3 }}> - <Typography variant="subtitle2" sx={{ mb: 3, fontWeight: '600' }}> - Unlocked transferable tokens - </Typography> - - <Typography - data-testid="refresh-success" - sx={{ color: 'text.primary', fontWeight: '600', fontSize: 28 }} - variant="h5" - textTransform="uppercase" - > - {userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.display_mix_denom.toUpperCase()} - </Typography> - </Box> - ); -}; - -export const VestingCard = ({ onTransfer }: { onTransfer: () => Promise<void> }) => { - const { userBalance } = useContext(AppContext); - const { enqueueSnackbar, closeSnackbar } = useSnackbar(); - - const refreshBalances = async () => { - await userBalance.fetchBalance(); - await userBalance.fetchTokenAllocation(); - }; - - useEffect(() => { - closeSnackbar(); - userBalance.fetchTokenAllocation(); - }, []); - - if (!userBalance.originalVesting) return null; - - return ( - <NymCard - title="Vesting Schedule" - subheader={ - <Typography variant="caption" sx={{ color: 'nym.text.muted' }}> - You can use up to 10% of your locked tokens for bonding and delegating - </Typography> - } - borderless - data-testid="check-unvested-tokens" - Action={ - <IconButton - onClick={async () => { - await refreshBalances(); - enqueueSnackbar('Balances updated', { variant: 'success', preventDuplicate: true }); - }} - > - <Refresh /> - </IconButton> - } - > - <VestingSchedule /> - <TokenTransfer /> - <Box display="flex" justifyContent="end" alignItems="center"> - <Button size="large" variant="contained" onClick={onTransfer} disableElevation> - Transfer - </Button> - </Box> - </NymCard> - ); -}; diff --git a/nym-wallet/src/pages/bonding/Bonding.tsx b/nym-wallet/src/pages/bonding/Bonding.tsx index 1aa6bd5ef2..957f603d0e 100644 --- a/nym-wallet/src/pages/bonding/Bonding.tsx +++ b/nym-wallet/src/pages/bonding/Bonding.tsx @@ -17,6 +17,7 @@ import { AppContext, urls } from 'src/context/main'; import { isGateway, isMixnode, TBondGatewayArgs, TBondMixNodeArgs, TBondMoreArgs } from 'src/types'; import { BondedGateway } from 'src/components/Bonding/BondedGateway'; import { RedeemRewardsModal } from 'src/components/Bonding/modals/RedeemRewardsModal'; +import { Console } from 'src/utils/console'; import { BondingContextProvider, useBondingContext } from '../../context'; import { getMixnodeStakeSaturation } from '../../requests'; @@ -99,12 +100,12 @@ const Bonding = () => { (await getMixnodeStakeSaturation(newMixId)).uncapped_saturation, ); if (newSaturation && newSaturation > 1) { - const saturationPercentage = Math.round(newSaturation * 100); - return { isOverSaturated: true, saturationPercentage }; + const newSaturationPercentage = Math.round(newSaturation * 100); + return { isOverSaturated: true, saturationPercentage: newSaturationPercentage }; } return { isOverSaturated: false, saturationPercentage: undefined }; } catch (e) { - console.error('Error fetching the saturation, error:', e); + Console.error('Error fetching the saturation, error:', e); return { isOverSaturated: false, saturationPercentage: undefined }; } }; @@ -113,10 +114,12 @@ const Bonding = () => { switch (action) { case 'bondMore': { if (bondedNode && isMixnode(bondedNode)) { - const { isOverSaturated, saturationPercentage } = await handleCheckStakeSaturation(bondedNode.mixId); - if (isOverSaturated && saturationPercentage) { + const { isOverSaturated, saturationPercentage: newSaturationPercentage } = await handleCheckStakeSaturation( + bondedNode.mixId, + ); + if (isOverSaturated && newSaturationPercentage) { setShowModal('bond-more-oversaturated'); - setSaturationPercentage(saturationPercentage.toString()); + setSaturationPercentage(newSaturationPercentage.toString()); break; } } @@ -178,7 +181,7 @@ const Bonding = () => { {showModal === 'bond-more-oversaturated' && saturationPercentage && ( <BondOversaturatedModal - open={true} + open onClose={() => setShowModal(undefined)} onContinue={() => setShowModal('bond-more')} saturationPercentage={saturationPercentage} diff --git a/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx index da50bc2d2d..a390c0e7c0 100644 --- a/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx @@ -10,13 +10,15 @@ import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { NymCard } from 'src/components'; import { PageLayout } from 'src/layouts'; import { Tabs } from 'src/components/Tabs'; -import { useBondingContext, BondingContextProvider } from 'src/context'; +import { useBondingContext, BondingContextProvider, TBondedMixnode } from 'src/context'; import { AppContext, urls } from 'src/context/main'; +import { isMixnode } from 'src/types'; +import { getIntervalAsDate } from 'src/utils'; import { NodeGeneralSettings } from './settings-pages/general-settings'; import { NodeUnbondPage } from './settings-pages/NodeUnbondPage'; import { createNavItems } from './node-settings.constant'; -import { isMixnode } from 'src/types'; +import { ApyPlayground } from './apy-playground'; export const NodeSettings = () => { const theme = useTheme(); @@ -25,7 +27,7 @@ export const NodeSettings = () => { const navigate = useNavigate(); const location = useLocation(); - const [confirmationDetails, setConfirmationDetails] = useState<ConfirmationDetailProps>(); + const [confirmationDetails, setConfirmationDetails] = useState<ConfirmationDetailProps | undefined>(); const [value, setValue] = React.useState('General'); const handleChange = (event: React.SyntheticEvent, tab: string) => { setValue(tab); @@ -39,9 +41,11 @@ export const NodeSettings = () => { const handleUnbond = async (fee?: FeeDetails) => { const tx = await unbond(fee); + const { nextEpoch } = await getIntervalAsDate(); setConfirmationDetails({ status: 'success', title: 'Unbond successful', + subtitle: `This operation will complete when the new epoch starts at: ${nextEpoch}`, txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`, }); }; @@ -123,6 +127,7 @@ export const NodeSettings = () => { {value === 'Unbond' && bondedNode && ( <NodeUnbondPage bondedNode={bondedNode} onConfirm={handleUnbond} onError={handleError} /> )} + {value === 'Playground' && bondedNode && <ApyPlayground bondedNode={bondedNode as TBondedMixnode} />} {confirmationDetails && confirmationDetails.status === 'success' && ( <ConfirmationDetailsModal title={confirmationDetails.title} @@ -133,7 +138,12 @@ export const NodeSettings = () => { setConfirmationDetails(undefined); navigate('/bonding'); }} - /> + > + <Typography fontWeight="bold"> + You should NOT shutdown your {isMixnode(bondedNode) ? 'mix node' : 'gateway'} until the unbond process is + complete + </Typography> + </ConfirmationDetailsModal> )} </NymCard> </PageLayout> diff --git a/nym-wallet/src/pages/bonding/node-settings/apy-playground/index.tsx b/nym-wallet/src/pages/bonding/node-settings/apy-playground/index.tsx new file mode 100644 index 0000000000..65edb35bc5 --- /dev/null +++ b/nym-wallet/src/pages/bonding/node-settings/apy-playground/index.tsx @@ -0,0 +1,140 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Card, CardContent, CardHeader, Grid, Typography } from '@mui/material'; +import { ResultsTable } from 'src/components/RewardsPlayground/ResultsTable'; +import { getDelegationSummary } from 'src/requests'; +import { NodeDetails } from 'src/components/RewardsPlayground/NodeDetail'; +import { CalculateArgs, Inputs } from 'src/components/RewardsPlayground/Inputs'; +import { TBondedMixnode } from 'src/context'; +import { useSnackbar } from 'notistack'; +import { LoadingModal } from 'src/components/Modals/LoadingModal'; +import { Console } from 'src/utils/console'; +import { computeEstimate, computeStakeSaturation, handleCalculatePeriodRewards } from './utils'; + +export type DefaultInputValues = { + profitMargin: string; + uptime: string; + bond: string; + delegations: string; + operatorCost: string; +}; + +export const ApyPlayground = ({ bondedNode }: { bondedNode: TBondedMixnode }) => { + const { enqueueSnackbar } = useSnackbar(); + + const [results, setResults] = useState({ + total: { daily: '-', monthly: '-', yearly: '-' }, + operator: { daily: '-', monthly: '-', yearly: '-' }, + delegator: { daily: '-', monthly: '-', yearly: '-' }, + }); + + const [defaultInputValues, setDefaultInputValues] = useState<DefaultInputValues>(); + const [stakeSaturation, setStakeSaturation] = useState<string>(); + const [isLoading, setIsLoading] = useState(true); + + const initialise = async (node: TBondedMixnode) => { + try { + const delegations = await getDelegationSummary(); + + const { estimation } = await computeEstimate({ + mixId: node.mixId, + uptime: node.uptime.toString(), + profitMargin: node.profitMargin, + operatorCost: node.operatorCost.amount, + totalDelegation: delegations.total_delegations.amount, + pledgeAmount: node.bond.amount, + }); + + setResults( + handleCalculatePeriodRewards({ + estimatedOperatorReward: estimation.operator, + estimatedDelegatorsReward: estimation.delegates, + }), + ); + + setStakeSaturation(node.stakeSaturation); + + setDefaultInputValues({ + profitMargin: node.profitMargin, + uptime: (node.uptime || 0).toString(), + bond: node.bond.amount || '', + delegations: delegations.total_delegations.amount, + operatorCost: node.operatorCost.amount, + }); + setIsLoading(false); + } catch (e) { + enqueueSnackbar(e as string, { variant: 'error' }); + } + }; + + useEffect(() => { + if (bondedNode) { + initialise(bondedNode); + } + }, []); + + if (isLoading) return <LoadingModal />; + + const handleCalculateEstimate = async ({ bond, delegations, uptime, profitMargin, operatorCost }: CalculateArgs) => { + try { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { estimation, reward_params } = await computeEstimate({ + mixId: bondedNode.mixId, + uptime, + profitMargin, + operatorCost, + totalDelegation: delegations, + pledgeAmount: bond, + }); + + const estimationResult = handleCalculatePeriodRewards({ + estimatedOperatorReward: estimation.operator, + estimatedDelegatorsReward: estimation.delegates, + }); + + const computedStakeSaturation = computeStakeSaturation( + bond, + delegations, + reward_params.interval.stake_saturation_point, + ); + + setStakeSaturation(computedStakeSaturation); + setResults(estimationResult); + } catch (e) { + Console.log(e); + } + }; + + return ( + <Box sx={{ p: 3 }}> + <Typography fontWeight="medium" sx={{ mb: 1 }}> + Playground + </Typography> + <Typography variant="body2" sx={{ color: 'grey.600', mb: 2 }}> + This is your parameters playground - change the parameters below to see the node specific estimations in the + table + </Typography> + {defaultInputValues && ( + <Card variant="outlined" sx={{ p: 1, mb: 3 }}> + <CardHeader + title={ + <Typography variant="body2" fontWeight="medium"> + Estimation calculator + </Typography> + } + /> + <CardContent> + <Inputs onCalculate={handleCalculateEstimate} defaultValues={defaultInputValues} /> + </CardContent> + </Card> + )} + <Grid container spacing={3}> + <Grid item xs={12} md={8}> + <ResultsTable results={results} /> + </Grid> + <Grid item xs={12} md={4}> + <NodeDetails saturation={stakeSaturation} /> + </Grid> + </Grid> + </Box> + ); +}; diff --git a/nym-wallet/src/pages/bonding/node-settings/apy-playground/utils.tsx b/nym-wallet/src/pages/bonding/node-settings/apy-playground/utils.tsx new file mode 100644 index 0000000000..c86be7b22d --- /dev/null +++ b/nym-wallet/src/pages/bonding/node-settings/apy-playground/utils.tsx @@ -0,0 +1,66 @@ +import { decimalToPercentage, percentToDecimal } from '@nymproject/types'; +import { computeMixnodeRewardEstimation } from 'src/requests'; + +const SCALE_FACTOR = 1_000_000; + +export const computeStakeSaturation = (bond: string, delegations: string, stakeSaturationPoint: string) => { + const res = ((+bond + +delegations) * SCALE_FACTOR) / +stakeSaturationPoint; + return decimalToPercentage(res.toFixed(18).toString()); +}; + +export const computeEstimate = async ({ + mixId, + uptime, + pledgeAmount, + totalDelegation, + profitMargin, + operatorCost, +}: { + mixId: number; + uptime: string; + pledgeAmount: string; + totalDelegation: string; + profitMargin: string; + operatorCost: string; +}) => { + const computedEstimate = await computeMixnodeRewardEstimation({ + mixId, + performance: percentToDecimal(uptime), + pledgeAmount: Math.round(+pledgeAmount * SCALE_FACTOR), + totalDelegation: Math.round(+totalDelegation * SCALE_FACTOR), + profitMarginPercent: percentToDecimal(profitMargin), + intervalOperatingCost: { denom: 'unym', amount: Math.round(+operatorCost * SCALE_FACTOR).toString() }, + }); + + return computedEstimate; +}; + +export const handleCalculatePeriodRewards = ({ + estimatedOperatorReward, + estimatedDelegatorsReward, +}: { + estimatedOperatorReward: string; + estimatedDelegatorsReward: string; +}) => { + const dailyOperatorReward = (+estimatedOperatorReward / SCALE_FACTOR) * 24; // epoch_reward * 1 epoch_per_hour * 24 hours + const dailyDelegatorReward = (+estimatedDelegatorsReward / SCALE_FACTOR) * 24; + const dailyTotal = dailyOperatorReward + dailyDelegatorReward; + + return { + total: { + daily: dailyTotal.toFixed(3).toString(), + monthly: (dailyTotal * 30).toFixed(3).toString(), + yearly: (dailyTotal * 365).toFixed(3).toString(), + }, + operator: { + daily: dailyOperatorReward.toFixed(3).toString(), + monthly: (dailyOperatorReward * 30).toFixed(3).toString(), + yearly: (dailyOperatorReward * 365).toFixed(3).toString(), + }, + delegator: { + daily: dailyDelegatorReward.toFixed(3).toString(), + monthly: (dailyDelegatorReward * 30).toFixed(3).toString(), + yearly: (dailyDelegatorReward * 365).toFixed(3).toString(), + }, + }; +}; diff --git a/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx b/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx index f00409a182..0e8b6204fd 100644 --- a/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx @@ -1,5 +1,5 @@ export const createNavItems = (isMixnode: boolean) => { const navItems = ['Unbond']; - if (isMixnode) return ['General', ...navItems]; + if (isMixnode) return ['General', 'Playground', ...navItems]; return navItems; }; diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/NodeUnbondPage.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/NodeUnbondPage.tsx index 347ed2467a..7cccb8d3f7 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/NodeUnbondPage.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/NodeUnbondPage.tsx @@ -4,6 +4,7 @@ import { TBondedMixnode, TBondedGateway } from 'src/context/bonding'; import { Error } from 'src/components/Error'; import { UnbondModal } from 'src/components/Bonding/modals/UnbondModal'; import { isMixnode } from 'src/types'; + interface Props { bondedNode: TBondedMixnode | TBondedGateway; @@ -13,7 +14,7 @@ interface Props { export const NodeUnbondPage = ({ bondedNode, onConfirm, onError }: Props) => { const [confirmField, setConfirmField] = useState(''); const [isConfirmed, setIsConfirmed] = useState(false); - //TODO: Check what happens with a gateway + // TODO: Check what happens with a gateway return ( <Box sx={{ p: 3, minHeight: '450px' }}> <Grid container justifyContent="space-between"> diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/InfoSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/InfoSettings.tsx index 31e88a0f31..00a409dab2 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/InfoSettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/InfoSettings.tsx @@ -1,11 +1,11 @@ -import { useState } from 'react'; +import React, { useState } from 'react'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -import { Button, Divider, Typography, TextField, Grid, CircularProgress, Box } from '@mui/material'; +import { Box, Button, Divider, Grid, TextField, Typography } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import { isMixnode } from 'src/types'; import { simulateUpdateMixnodeConfig, simulateVestingUpdateMixnodeConfig, updateMixnodeConfig } from 'src/requests'; -import { TBondedMixnode, TBondedGateway } from 'src/context/bonding'; +import { TBondedGateway, TBondedMixnode } from 'src/context/bonding'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { bondedInfoParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema'; import { Console } from 'src/utils/console'; diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx index 7aa7407a48..0bff6c3ea3 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx @@ -1,24 +1,12 @@ import React, { useContext, useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -import { - Button, - Divider, - Typography, - TextField, - InputAdornment, - Grid, - CircularProgress, - Box, - FormHelperText, -} from '@mui/material'; +import { Box, Button, Divider, FormHelperText, Grid, InputAdornment, TextField, Typography } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; -import { add, format, fromUnixTime } from 'date-fns'; import { isMixnode } from 'src/types'; import { - getCurrentInterval, getPendingIntervalEvents, simulateUpdateMixnodeCostParams, simulateVestingUpdateMixnodeCostParams, @@ -29,6 +17,7 @@ import { TBondedMixnode } from 'src/context/bonding'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema'; import { Console } from 'src/utils/console'; +import { getIntervalAsDate } from 'src/utils'; import { Alert } from 'src/components/Alert'; import { ChangeMixCostParams } from 'src/pages/bonding/types'; import { AppContext } from 'src/context'; @@ -39,7 +28,6 @@ import { LoadingModal } from 'src/components/Modals/LoadingModal'; export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => { const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false); const [intervalTime, setIntervalTime] = useState<string>(); - const [nextEpoch, setNextEpoch] = useState<string>(); const [pendingUpdates, setPendingUpdates] = useState<MixNodeCostParams>(); const { clientDetails } = useContext(AppContext); const theme = useTheme(); @@ -63,27 +51,13 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode defaultValues, }); - const getIntervalAsDate = async () => { - const interval = await getCurrentInterval(); - const secondsToNextInterval = - Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds); - - setIntervalTime( - format( - add(new Date(), { - seconds: secondsToNextInterval, - }), - 'MM/dd/yyyy HH:mm', - ), - ); - setNextEpoch( - format( - add(fromUnixTime(Number(interval.current_epoch_start_unix)), { - seconds: Number(interval.epoch_length_seconds), - }), - 'HH:mm', - ), - ); + const getCurrentInterval = async () => { + try { + const { nextInterval } = await getIntervalAsDate(); + setIntervalTime(nextInterval); + } catch { + Console.log('cant retrieve next interval'); + } }; const getPendingEvents = async () => { @@ -107,14 +81,14 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }; useEffect(() => { - getIntervalAsDate(); + getCurrentInterval(); getPendingEvents(); }, []); const onSubmit = async (data: { operatorCost: { amount: string; denom: CurrencyDenom }; profitMargin: string }) => { resetFeeState(); if (data.operatorCost && data.profitMargin) { - const MixNodeCostParams = { + const mixNodeCostParams = { profit_margin_percent: (+data.profitMargin / 100).toString(), interval_operating_cost: { amount: data.operatorCost.amount, @@ -123,9 +97,9 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }; try { if (bondedNode.proxy) { - await vestingUpdateMixnodeCostParams(MixNodeCostParams); + await vestingUpdateMixnodeCostParams(mixNodeCostParams); } else { - await updateMixnodeCostParams(MixNodeCostParams); + await updateMixnodeCostParams(mixNodeCostParams); } await getPendingEvents(); reset(); @@ -137,7 +111,16 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }; return ( - <Grid container xs item> + <Grid + container + xs + item + sx={{ + '& .MuiGrid-item': { + pl: 0, + }, + }} + > {fee && ( <ConfirmTx open @@ -149,16 +132,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode /> )} {isSubmitting && <LoadingModal />} - <Alert - title={ - <> - <Box component="span" sx={{ fontWeight: 600, mr: 2 }}> - {`Next epoch ${nextEpoch}`} - </Box> - <Box component="span" sx={{ fontWeight: 600 }}>{`Next interval: ${intervalTime}`}</Box> - </> - } - /> + <Alert title={<Box component="span" sx={{ fontWeight: 600 }}>{`Next interval: ${intervalTime}`}</Box>} /> <Grid container direction="column"> <Grid item container alignItems="left" justifyContent="space-between" padding={3} spacing={1}> <Grid item xl={6}> @@ -177,7 +151,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode </Typography> </Grid> {isMixnode(bondedNode) && ( - <Grid item xs={12} xl={6}> + <Grid item xs={12} md={6}> <TextField {...register('profitMargin')} name="profitMargin" @@ -206,7 +180,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode </Grid> )} </Grid> - <Divider flexItem /> + <Divider flexItem sx={{ position: 'relative', left: '-24px', width: 'calc(100% + 24px)' }} /> <Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3} spacing={1}> <Grid item> <Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}> @@ -223,33 +197,31 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode Changes to cost will be applied in the next interval. </Typography> </Grid> - <Grid spacing={3} container item alignItems="center" xs={12} md={6}> - <Grid item width={1}> - <CurrencyFormField - required - fullWidth - label="Operating cost" - onChanged={(newValue) => { - setValue('operatorCost', newValue, { shouldValidate: true, shouldDirty: true }); - }} - validationError={errors.operatorCost?.amount?.message} - denom={clientDetails?.display_mix_denom || 'nym'} - initialValue={defaultValues.operatorCost.amount} - /> - {pendingUpdates && ( - <FormHelperText> - Your last change to{' '} - <Typography variant="caption" fontWeight="bold"> - {pendingUpdates.interval_operating_cost.amount}{' '} - {pendingUpdates?.interval_operating_cost.denom.toUpperCase()}{' '} - </Typography> - will be applied in the next interval - </FormHelperText> - )} - </Grid> + <Grid item xs={12} md={6}> + <CurrencyFormField + required + fullWidth + label="Operating cost" + onChanged={(newValue) => { + setValue('operatorCost', newValue, { shouldValidate: true, shouldDirty: true }); + }} + validationError={errors.operatorCost?.amount?.message} + denom={clientDetails?.display_mix_denom || 'nym'} + initialValue={defaultValues.operatorCost.amount} + /> + {pendingUpdates && ( + <FormHelperText> + Your last change to{' '} + <Typography variant="caption" fontWeight="bold"> + {pendingUpdates.interval_operating_cost.amount}{' '} + {pendingUpdates?.interval_operating_cost.denom.toUpperCase()}{' '} + </Typography> + will be applied in the next interval + </FormHelperText> + )} </Grid> </Grid> - <Divider flexItem /> + <Divider flexItem sx={{ position: 'relative', left: '-24px', width: 'calc(100% + 24px)' }} /> <Grid container justifyContent="end"> <Button size="large" diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/index.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/index.tsx index 6597c096f7..edc8ac0f45 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/index.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/index.tsx @@ -1,15 +1,15 @@ import React, { useState } from 'react'; import { Box, Button, Divider, Grid } from '@mui/material'; +import { isMixnode } from 'src/types'; import { TBondedMixnode, TBondedGateway } from '../../../../../context/bonding'; import { InfoSettings } from './InfoSettings'; import { ParametersSettings } from './ParametersSettings'; -import { isMixnode } from 'src/types'; const nodeGeneralNav = ['Info', 'Parameters']; export const NodeGeneralSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => { const [settingsCard, setSettingsCard] = useState<string>(nodeGeneralNav[0]); - //TODO: Check what happens with a gateway + // TODO: Check what happens with a gateway return ( <Box sx={{ pl: 3, pt: 3 }}> <Grid container direction="row" spacing={3}> diff --git a/nym-wallet/src/pages/delegation/index.tsx b/nym-wallet/src/pages/delegation/index.tsx index f2a668e59e..05914742eb 100644 --- a/nym-wallet/src/pages/delegation/index.tsx +++ b/nym-wallet/src/pages/delegation/index.tsx @@ -1,6 +1,6 @@ import React, { FC, useContext, useEffect, useState } from 'react'; -import { Box, Button, Paper, Stack, Typography, CircularProgress } from '@mui/material'; -import { useTheme, Theme } from '@mui/material/styles'; +import { Box, Button, CircularProgress, Paper, Stack, Typography } from '@mui/material'; +import { Theme, useTheme } from '@mui/material/styles'; import { DecCoin, decimalToFloatApproximation, DelegationWithEverything, FeeDetails } from '@nymproject/types'; import { Link } from '@nymproject/react/link/Link'; import { AppContext, urls } from 'src/context/main'; @@ -9,8 +9,9 @@ import { TPoolOption } from 'src/components'; import { Console } from 'src/utils/console'; import { OverSaturatedBlockerModal } from 'src/components/Delegation/DelegateBlocker'; import { getSpendableCoins, userBalance } from 'src/requests'; +import { getIntervalAsDate, toPercentIntegerString } from 'src/utils'; import { RewardsSummary } from '../../components/Rewards/RewardsSummary'; -import { DelegationContextProvider, useDelegationContext, TDelegations } from '../../context/delegations'; +import { DelegationContextProvider, TDelegations, useDelegationContext } from '../../context/delegations'; import { RewardsContextProvider, useRewardsContext } from '../../context/rewards'; import { DelegateModal } from '../../components/Delegation/DelegateModal'; import { UndelegateModal } from '../../components/Delegation/UndelegateModal'; @@ -18,7 +19,6 @@ import { DelegationListItemActions } from '../../components/Delegation/Delegatio import { RedeemModal } from '../../components/Rewards/RedeemModal'; import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal'; import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles'; -import { toPercentIntegerString } from '../../utils'; const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) => isStorybook @@ -36,9 +36,9 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { const [confirmationModalProps, setConfirmationModalProps] = useState<DelegationModalProps | undefined>(); const [currentDelegationListActionItem, setCurrentDelegationListActionItem] = useState<DelegationWithEverything>(); const [saturationError, setSaturationError] = useState<{ action: 'compound' | 'delegate'; saturation: string }>(); + const [nextEpoch, setNextEpoch] = useState<string | Error>(); const theme = useTheme(); - const { clientDetails, network, @@ -75,6 +75,15 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { }; }; + const getNextInterval = async () => { + try { + const { nextEpoch: newNextEpoch } = await getIntervalAsDate(); + setNextEpoch(newNextEpoch); + } catch { + setNextEpoch(Error()); + } + }; + // Refresh the rewards and delegations periodically when page is mounted useEffect(() => { const timer = setInterval(refresh, 1 * 60 * 1000); // every 1 minute @@ -83,6 +92,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { useEffect(() => { refresh(); + getNextInterval(); }, [clientDetails, confirmationModalProps]); const handleDelegationItemActionClick = (item: DelegationWithEverything, action: DelegationListItemActions) => { @@ -332,35 +342,45 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { <> <Paper elevation={0} sx={{ p: 3, mt: 4 }}> <Stack spacing={3}> - <Box display="flex" justifyContent="space-between" alignItems="center"> - <Typography variant="h6" lineHeight={1.334} fontWeight={600}> - Delegations - </Typography> + <Box display="flex" justifyContent="space-between"> + {' '} + <Box display="flex" flexDirection="column"> + <Typography variant="h6" lineHeight={1.334} fontWeight={600}> + Delegations + </Typography> + {!!delegations?.length && ( + <Link + href={`${urls(network).networkExplorer}/network-components/mixnodes/`} + target="_blank" + rel="noreferrer" + text="Network Explorer" + fontSize={14} + fontWeight={theme.palette.mode === 'light' ? 400 : 600} + noIcon + marginTop={1.5} + /> + )} + </Box> {!!delegations?.length && ( - <Link - href={`${urls(network).networkExplorer}/network-components/mixnodes/`} - target="_blank" - rel="noreferrer" - text="Network Explorer" - fontSize={14} - fontWeight={theme.palette.mode === 'light' ? 400 : 600} - noIcon - /> + <Button + variant="contained" + disableElevation + onClick={() => setShowNewDelegationModal(true)} + sx={{ py: 1.5, px: 5, color: 'primary.contrastText', height: 'fit-content' }} + > + Delegate + </Button> )} </Box> {!!delegations?.length && ( <Box display="flex" justifyContent="space-between" alignItems="end"> <RewardsSummary isLoading={isLoading} totalDelegation={totalDelegations} totalRewards={totalRewards} /> - - <Button - variant="contained" - disableElevation - onClick={() => setShowNewDelegationModal(true)} - sx={{ py: 1.5, px: 5, color: 'primary.contrastText' }} - > - Delegate - </Button> + {nextEpoch instanceof Error ? null : ( + <Typography fontSize={14}> + Next epoch starts at <b>{nextEpoch}</b> + </Typography> + )} </Box> )} {delegationsComponent(delegations)} diff --git a/nym-wallet/src/pages/index.ts b/nym-wallet/src/pages/index.ts index c2944d771c..21d21cf7b5 100644 --- a/nym-wallet/src/pages/index.ts +++ b/nym-wallet/src/pages/index.ts @@ -1,4 +1,3 @@ -export * from './auth'; export * from './Admin'; export * from './balance'; export * from './bonding'; diff --git a/nym-wallet/src/pages/internal-docs/DocEntry.tsx b/nym-wallet/src/pages/internal-docs/DocEntry.tsx index 43c26b6491..22374cc92c 100644 --- a/nym-wallet/src/pages/internal-docs/DocEntry.tsx +++ b/nym-wallet/src/pages/internal-docs/DocEntry.tsx @@ -34,7 +34,7 @@ function collectArgs(functionName: string, args: ArgDef[]) { return invokeArgs; } -export const DocEntry: React.FC<DocEntryProps> = (props) => { +export const DocEntry: FCWithChildren<DocEntryProps> = (props) => { const [card, setCard] = React.useState(<Card />); const onClick = () => { diff --git a/nym-wallet/src/pages/settings/tabs.tsx b/nym-wallet/src/pages/settings/tabs.tsx index 07a4ab07cb..13d28f3da1 100644 --- a/nym-wallet/src/pages/settings/tabs.tsx +++ b/nym-wallet/src/pages/settings/tabs.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Tab, Tabs as MuiTabs, Box } from '@mui/material'; -export const Tabs: React.FC<{ +export const Tabs: FCWithChildren<{ tabs: string[]; selectedTab: number; disabled: boolean; @@ -24,4 +24,4 @@ export const Tabs: React.FC<{ </MuiTabs> ); -export const TabPanel: React.FC = ({ children }) => <Box sx={{ p: 4 }}>{children}</Box>; +export const TabPanel: FCWithChildren = ({ children }) => <Box sx={{ p: 4 }}>{children}</Box>; diff --git a/nym-wallet/src/pages/terminal/index.tsx b/nym-wallet/src/pages/terminal/index.tsx index 3bd322318e..d027e782b9 100644 --- a/nym-wallet/src/pages/terminal/index.tsx +++ b/nym-wallet/src/pages/terminal/index.tsx @@ -10,8 +10,9 @@ import { AppContext } from '../../context/main'; import { NymCard } from '../../components'; import { getCurrentInterval, getAllPendingDelegations, getMixNodeDelegationsForCurrentAccount } from '../../requests'; -const TerminalSection: React.FC<{ +const TerminalSection: FCWithChildren<{ heading: React.ReactNode; + children: React.ReactNode; }> = ({ heading, children }) => { const [isCollapsed, setIsCollapsed] = useState<boolean>(true); @@ -32,7 +33,7 @@ const TerminalSection: React.FC<{ ); }; -const TerminalInner: React.FC = () => { +const TerminalInner: FCWithChildren = () => { const { network, userBalance, clientDetails, handleShowTerminal, appEnv } = useContext(AppContext); const [mixnodeDelegations, setMixnodeDelegations] = useState<any>(); const [pendingEvents, setPendingEvents] = useState<any>(); @@ -184,7 +185,7 @@ const TerminalInner: React.FC = () => { ); }; -export const Terminal: React.FC = () => { +export const Terminal: FCWithChildren = () => { const { showTerminal } = useContext(AppContext); // this is a guard component, that only mounts the terminal component when shown diff --git a/nym-wallet/src/requests/actions.ts b/nym-wallet/src/requests/actions.ts index b4aaa144c0..db4e715fb4 100644 --- a/nym-wallet/src/requests/actions.ts +++ b/nym-wallet/src/requests/actions.ts @@ -33,5 +33,4 @@ export const unbond = async (type: EnumNodeType) => { return unbondGateway(); }; -export const bondMore = async (args: TBondMoreArgs) => - invokeWrapper<TransactionExecuteResult>('pledge_more', args); +export const bondMore = async (args: TBondMoreArgs) => invokeWrapper<TransactionExecuteResult>('pledge_more', args); diff --git a/nym-wallet/src/requests/queries.ts b/nym-wallet/src/requests/queries.ts index 5fba51beec..be74041f33 100644 --- a/nym-wallet/src/requests/queries.ts +++ b/nym-wallet/src/requests/queries.ts @@ -1,13 +1,13 @@ import { - StakeSaturationResponse, - MixnodeStatusResponse, - InclusionProbabilityResponse, DecCoin, - MixNodeDetails, GatewayBond, - RewardEstimationResponse, - WrappedDelegationEvent, + InclusionProbabilityResponse, + MixNodeDetails, + MixnodeStatusResponse, PendingIntervalEvent, + RewardEstimationResponse, + StakeSaturationResponse, + WrappedDelegationEvent, } from '@nymproject/types'; import { Interval, TGatewayReport, TNodeDescription } from 'src/types'; import { invokeWrapper } from './wrapper'; @@ -51,3 +51,13 @@ export const getPendingIntervalEvents = async () => export const getGatewayReport = async (identity: string) => invokeWrapper<TGatewayReport>('gateway_report', { identity }); + +export const computeMixnodeRewardEstimation = async (args: { + mixId: number; + performance: string; + pledgeAmount: number; + totalDelegation: number; + profitMarginPercent: string; + intervalOperatingCost: { denom: 'unym'; amount: string }; +}) => invokeWrapper<RewardEstimationResponse>('compute_mixnode_reward_estimation', args); +export const getMixnodeUptime = async (mixId: number) => invokeWrapper<number>('get_mixnode_uptime', { mixId }); diff --git a/nym-wallet/src/requests/rewards.ts b/nym-wallet/src/requests/rewards.ts index 5c8c63e34e..36b90838e7 100644 --- a/nym-wallet/src/requests/rewards.ts +++ b/nym-wallet/src/requests/rewards.ts @@ -1,4 +1,4 @@ -import { Fee, FeeDetails, TransactionExecuteResult } from '@nymproject/types'; +import { Fee, FeeDetails, RewardingParams, TransactionExecuteResult } from '@nymproject/types'; import { invokeWrapper } from './wrapper'; export const claimOperatorReward = async (fee?: Fee) => @@ -9,3 +9,6 @@ export const claimDelegatorRewards = async (mixId: number, fee?: FeeDetails) => mixId, fee: fee?.fee, }); + +export const getCurrentRewardingParameter = async () => + invokeWrapper<RewardingParams>('get_current_rewarding_parameters', {}); diff --git a/nym-wallet/src/requests/simulate.ts b/nym-wallet/src/requests/simulate.ts index 4a1b82f0d4..d5aeacc2eb 100644 --- a/nym-wallet/src/requests/simulate.ts +++ b/nym-wallet/src/requests/simulate.ts @@ -59,13 +59,15 @@ export const simulateWithdrawVestedCoins = async (args: any) => export const simulateSend = async ({ address, amount }: { address: string; amount: DecCoin }) => invokeWrapper<FeeDetails>('simulate_send', { address, amount }); +export const getCustomFees = async ({ feesAmount }: { feesAmount: DecCoin }) => + invokeWrapper<FeeDetails>('get_custom_fees', { feesAmount }); + export const simulateClaimOperatorReward = async () => invokeWrapper<FeeDetails>('simulate_claim_operator_reward'); export const simulateVestingClaimOperatorReward = async () => invokeWrapper<FeeDetails>('simulate_vesting_claim_operator_reward'); -export const simulateBondMore = async (args: any) => - invokeWrapper<FeeDetails>('simulate_pledge_more', args); +export const simulateBondMore = async (args: any) => invokeWrapper<FeeDetails>('simulate_pledge_more', args); export const simulateVestingBondMore = async (args: any) => invokeWrapper<FeeDetails>('simulate_vesting_pledge_more', args); diff --git a/nym-wallet/src/requests/vesting.ts b/nym-wallet/src/requests/vesting.ts index bad2f7febf..4c64e3f63d 100644 --- a/nym-wallet/src/requests/vesting.ts +++ b/nym-wallet/src/requests/vesting.ts @@ -18,6 +18,10 @@ export const getLockedCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoi export const getSpendableCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_coins'); +export const getSpendableVestedCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_vested_coins'); + +export const getSpendableRewardCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_reward_coins'); + export const getVestingCoins = async (vestingAccountAddress: string): Promise<DecCoin> => invokeWrapper<DecCoin>('vesting_coins', { vestingAccountAddress }); @@ -106,13 +110,8 @@ export const vestingClaimOperatorReward = async (fee?: Fee) => export const vestingClaimDelegatorRewards = async (mixId: number) => invokeWrapper<TransactionExecuteResult>('vesting_claim_delegator_reward', { mixId }); -export const vestingBondMore = async ({ - fee, - additionalPledge, -}: { - fee?: Fee; - additionalPledge: DecCoin; -}) => invokeWrapper<TransactionExecuteResult>('vesting_pledge_more', { - fee, - additionalPledge, -}); +export const vestingBondMore = async ({ fee, additionalPledge }: { fee?: Fee; additionalPledge: DecCoin }) => + invokeWrapper<TransactionExecuteResult>('vesting_pledge_more', { + fee, + additionalPledge, + }); diff --git a/nym-wallet/src/svg-icons/node.tsx b/nym-wallet/src/svg-icons/node.tsx index e0ae33eb34..906cc286b8 100644 --- a/nym-wallet/src/svg-icons/node.tsx +++ b/nym-wallet/src/svg-icons/node.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { SvgIcon, SvgIconProps } from '@mui/material'; -export const Node: React.FC<SvgIconProps> = ({ color, ...props }) => ( +export const Node: FCWithChildren<SvgIconProps> = ({ color, ...props }) => ( <SvgIcon {...props}> <path fillRule="evenodd" diff --git a/nym-wallet/src/svg-icons/poweredByBity.tsx b/nym-wallet/src/svg-icons/poweredByBity.tsx index 3b061ae7d3..458a77369f 100644 --- a/nym-wallet/src/svg-icons/poweredByBity.tsx +++ b/nym-wallet/src/svg-icons/poweredByBity.tsx @@ -5,7 +5,7 @@ export const PoweredByBity = (props: SvgIconProps) => ( <SvgIcon {...props} inheritViewBox> <svg width="126" height="16" viewBox="0 0 126 16" xmlns="http://www.w3.org/2000/svg"> <path d="M6.60938 6.92969C6.60938 7.79688 6.3125 8.46484 5.71875 8.93359C5.12891 9.39844 4.2832 9.63086 3.18164 9.63086H2.17383V13H1.17773V4.43359H3.39844C5.53906 4.43359 6.60938 5.26562 6.60938 6.92969ZM2.17383 8.77539H3.07031C3.95312 8.77539 4.5918 8.63281 4.98633 8.34766C5.38086 8.0625 5.57812 7.60547 5.57812 6.97656C5.57812 6.41016 5.39258 5.98828 5.02148 5.71094C4.65039 5.43359 4.07227 5.29492 3.28711 5.29492H2.17383V8.77539ZM13.8047 9.7832C13.8047 10.8301 13.541 11.6484 13.0137 12.2383C12.4863 12.8242 11.7578 13.1172 10.8281 13.1172C10.2539 13.1172 9.74414 12.9824 9.29883 12.7129C8.85352 12.4434 8.50977 12.0566 8.26758 11.5527C8.02539 11.0488 7.9043 10.459 7.9043 9.7832C7.9043 8.73633 8.16602 7.92188 8.68945 7.33984C9.21289 6.75391 9.93945 6.46094 10.8691 6.46094C11.7676 6.46094 12.4805 6.75977 13.0078 7.35742C13.5391 7.95508 13.8047 8.76367 13.8047 9.7832ZM8.91211 9.7832C8.91211 10.6035 9.07617 11.2285 9.4043 11.6582C9.73242 12.0879 10.2148 12.3027 10.8516 12.3027C11.4883 12.3027 11.9707 12.0898 12.2988 11.6641C12.6309 11.2344 12.7969 10.6074 12.7969 9.7832C12.7969 8.9668 12.6309 8.34766 12.2988 7.92578C11.9707 7.5 11.4844 7.28711 10.8398 7.28711C10.2031 7.28711 9.72266 7.49609 9.39844 7.91406C9.07422 8.33203 8.91211 8.95508 8.91211 9.7832ZM20.7598 13L19.582 9.23242C19.5078 9.00195 19.3691 8.47852 19.166 7.66211H19.1191C18.9629 8.3457 18.8262 8.87305 18.709 9.24414L17.4961 13H16.3711L14.6191 6.57812H15.6387C16.0527 8.19141 16.3672 9.41992 16.582 10.2637C16.8008 11.1074 16.9258 11.6758 16.957 11.9688H17.0039C17.0469 11.7461 17.1152 11.459 17.209 11.1074C17.3066 10.752 17.3906 10.4707 17.4609 10.2637L18.6387 6.57812H19.6934L20.8418 10.2637C21.0605 10.9355 21.209 11.5 21.2871 11.957H21.334C21.3496 11.8164 21.3906 11.5996 21.457 11.3066C21.5273 11.0137 21.9355 9.4375 22.6816 6.57812H23.6895L21.9141 13H20.7598ZM27.5684 13.1172C26.6191 13.1172 25.8691 12.8281 25.3184 12.25C24.7715 11.6719 24.498 10.8691 24.498 9.8418C24.498 8.80664 24.752 7.98438 25.2598 7.375C25.7715 6.76562 26.457 6.46094 27.3164 6.46094C28.1211 6.46094 28.7578 6.72656 29.2266 7.25781C29.6953 7.78516 29.9297 8.48242 29.9297 9.34961V9.96484H25.5059C25.5254 10.7188 25.7148 11.291 26.0742 11.6816C26.4375 12.0723 26.9473 12.2676 27.6035 12.2676C28.2949 12.2676 28.9785 12.123 29.6543 11.834V12.7012C29.3105 12.8496 28.9844 12.9551 28.6758 13.0176C28.3711 13.084 28.002 13.1172 27.5684 13.1172ZM27.3047 7.27539C26.7891 7.27539 26.377 7.44336 26.0684 7.7793C25.7637 8.11523 25.584 8.58008 25.5293 9.17383H28.8867C28.8867 8.56055 28.75 8.0918 28.4766 7.76758C28.2031 7.43945 27.8125 7.27539 27.3047 7.27539ZM34.5234 6.46094C34.8086 6.46094 35.0645 6.48438 35.291 6.53125L35.1562 7.43359C34.8906 7.375 34.6562 7.3457 34.4531 7.3457C33.9336 7.3457 33.4883 7.55664 33.1172 7.97852C32.75 8.40039 32.5664 8.92578 32.5664 9.55469V13H31.5938V6.57812H32.3965L32.5078 7.76758H32.5547C32.793 7.34961 33.0801 7.02734 33.416 6.80078C33.752 6.57422 34.1211 6.46094 34.5234 6.46094ZM39.2051 13.1172C38.2559 13.1172 37.5059 12.8281 36.9551 12.25C36.4082 11.6719 36.1348 10.8691 36.1348 9.8418C36.1348 8.80664 36.3887 7.98438 36.8965 7.375C37.4082 6.76562 38.0938 6.46094 38.9531 6.46094C39.7578 6.46094 40.3945 6.72656 40.8633 7.25781C41.332 7.78516 41.5664 8.48242 41.5664 9.34961V9.96484H37.1426C37.1621 10.7188 37.3516 11.291 37.7109 11.6816C38.0742 12.0723 38.584 12.2676 39.2402 12.2676C39.9316 12.2676 40.6152 12.123 41.291 11.834V12.7012C40.9473 12.8496 40.6211 12.9551 40.3125 13.0176C40.0078 13.084 39.6387 13.1172 39.2051 13.1172ZM38.9414 7.27539C38.4258 7.27539 38.0137 7.44336 37.7051 7.7793C37.4004 8.11523 37.2207 8.58008 37.166 9.17383H40.5234C40.5234 8.56055 40.3867 8.0918 40.1133 7.76758C39.8398 7.43945 39.4492 7.27539 38.9414 7.27539ZM47.6016 12.1387H47.5488C47.0996 12.791 46.4277 13.1172 45.5332 13.1172C44.6934 13.1172 44.0391 12.8301 43.5703 12.2559C43.1055 11.6816 42.873 10.8652 42.873 9.80664C42.873 8.74805 43.1074 7.92578 43.5762 7.33984C44.0449 6.75391 44.6973 6.46094 45.5332 6.46094C46.4043 6.46094 47.0723 6.77734 47.5371 7.41016H47.6133L47.5723 6.94727L47.5488 6.49609V3.88281H48.5215V13H47.7305L47.6016 12.1387ZM45.6562 12.3027C46.3203 12.3027 46.8008 12.123 47.0977 11.7637C47.3984 11.4004 47.5488 10.8164 47.5488 10.0117V9.80664C47.5488 8.89648 47.3965 8.24805 47.0918 7.86133C46.791 7.4707 46.3086 7.27539 45.6445 7.27539C45.0742 7.27539 44.6367 7.49805 44.332 7.94336C44.0312 8.38477 43.8809 9.00977 43.8809 9.81836C43.8809 10.6387 44.0312 11.2578 44.332 11.6758C44.6328 12.0938 45.0742 12.3027 45.6562 12.3027ZM56.6953 6.47266C57.5391 6.47266 58.1934 6.76172 58.6582 7.33984C59.127 7.91406 59.3613 8.72852 59.3613 9.7832C59.3613 10.8379 59.125 11.6582 58.6523 12.2441C58.1836 12.8262 57.5312 13.1172 56.6953 13.1172C56.2773 13.1172 55.8945 13.041 55.5469 12.8887C55.2031 12.7324 54.9141 12.4941 54.6797 12.1738H54.6094L54.4043 13H53.707V3.88281H54.6797V6.09766C54.6797 6.59375 54.6641 7.03906 54.6328 7.43359H54.6797C55.1328 6.79297 55.8047 6.47266 56.6953 6.47266ZM56.5547 7.28711C55.8906 7.28711 55.4121 7.47852 55.1191 7.86133C54.8262 8.24023 54.6797 8.88086 54.6797 9.7832C54.6797 10.6855 54.8301 11.332 55.1309 11.7227C55.4316 12.1094 55.9141 12.3027 56.5781 12.3027C57.1758 12.3027 57.6211 12.0859 57.9141 11.6523C58.207 11.2148 58.3535 10.5879 58.3535 9.77148C58.3535 8.93555 58.207 8.3125 57.9141 7.90234C57.6211 7.49219 57.168 7.28711 56.5547 7.28711ZM60.0469 6.57812H61.0898L62.4961 10.2402C62.8047 11.0762 62.9961 11.6797 63.0703 12.0508H63.1172C63.168 11.8516 63.2734 11.5117 63.4336 11.0312C63.5977 10.5469 64.1289 9.0625 65.0273 6.57812H66.0703L63.3105 13.8906C63.0371 14.6133 62.7168 15.125 62.3496 15.4258C61.9863 15.7305 61.5391 15.8828 61.0078 15.8828C60.7109 15.8828 60.418 15.8496 60.1289 15.7832V15.0039C60.3438 15.0508 60.584 15.0742 60.8496 15.0742C61.5176 15.0742 61.9941 14.6992 62.2793 13.9492L62.6367 13.0352L60.0469 6.57812Z" /> - <g clip-path="url(#clip0_4705_34634)"> + <g clipPath="url(#clip0_4705_34634)"> <path d="M97.553 10.5857C97.553 9.53941 96.7819 9.24022 95.5098 9.24022H93.6675V11.9311H95.5098C96.7819 11.9311 97.553 11.632 97.553 10.5857ZM93.6663 5.40987V7.77381H95.239C96.3756 7.77381 97.1201 7.52859 97.1201 6.48232C97.1201 5.55865 96.2539 5.40987 95.239 5.40987H93.6663ZM99.2717 10.5857C99.2717 12.8826 97.7425 13.4531 95.5098 13.4531H92.5122C92.1723 13.4531 91.8965 13.1752 91.8965 12.8352V4.55977C91.8965 4.2181 92.1723 3.94182 92.5122 3.94182H95.239C97.35 3.94182 98.7033 4.7298 98.7033 6.48232C98.7033 7.4207 98.3108 8.0321 97.6207 8.3983C98.6356 8.60265 99.2717 9.1585 99.2717 10.5857Z" /> <path d="M103.709 13.5625C103.22 13.5625 102.822 13.1635 102.822 12.6712V4.73282C102.822 4.24219 103.22 3.84314 103.709 3.84314C104.199 3.84314 104.596 4.24219 104.596 4.73282V12.6712C104.596 13.1635 104.199 13.5625 103.709 13.5625Z" /> <path d="M114.992 3.95172H108.508C108.026 3.95172 107.636 4.34406 107.636 4.82795C107.636 5.31021 108.026 5.70255 108.508 5.70255H110.863V12.6732C110.863 13.1636 111.26 13.5625 111.75 13.5625C112.24 13.5625 112.637 13.1636 112.637 12.6732V5.70255H114.992C115.474 5.70255 115.865 5.31021 115.865 4.82795C115.865 4.34406 115.474 3.95172 114.992 3.95172Z" /> diff --git a/nym-wallet/src/theme/NymWalletTheme.tsx b/nym-wallet/src/theme/NymWalletTheme.tsx index 78672ff72c..4cb08eb645 100644 --- a/nym-wallet/src/theme/NymWalletTheme.tsx +++ b/nym-wallet/src/theme/NymWalletTheme.tsx @@ -4,7 +4,10 @@ import { createTheme, ThemeProvider } from '@mui/material/styles'; import { getDesignTokens } from './theme'; import '@assets/fonts/non-variable/fonts.css'; -export const NymWalletThemeWithMode: React.FC<{ mode: PaletteMode }> = ({ mode, children }) => { +export const NymWalletThemeWithMode: FCWithChildren<{ mode: PaletteMode; children: React.ReactNode }> = ({ + mode, + children, +}) => { const theme = React.useMemo(() => createTheme(getDesignTokens(mode)), [mode]); return ( <ThemeProvider theme={theme}> diff --git a/nym-wallet/src/theme/index.tsx b/nym-wallet/src/theme/index.tsx index b2dd6f2c65..007e1f4761 100644 --- a/nym-wallet/src/theme/index.tsx +++ b/nym-wallet/src/theme/index.tsx @@ -9,12 +9,12 @@ import { NymWalletThemeWithMode } from './NymWalletTheme'; * Provides the theme for the Network Explorer by reacting to the light/dark mode choice stored in the app context. */ -export const NymWalletTheme: React.FC = ({ children }) => { +export const NymWalletTheme: FCWithChildren = ({ children }) => { const { mode } = useContext(AppContext); return <NymWalletThemeWithMode mode={mode}>{children}</NymWalletThemeWithMode>; }; -export const AuthTheme: React.FC = ({ children }) => { +export const AuthTheme: FCWithChildren = ({ children }) => { const theme = createTheme(getDesignTokens('dark')); return ( <ThemeProvider theme={theme}> diff --git a/nym-wallet/src/types/global.ts b/nym-wallet/src/types/global.ts index 2c16901cfe..c5f0fc3a95 100644 --- a/nym-wallet/src/types/global.ts +++ b/nym-wallet/src/types/global.ts @@ -1,12 +1,4 @@ -import { - Gateway, - DecCoin, - MixNode, - PledgeData, - MixNodeCostParams, - PendingIntervalEventData, - PendingEpochEventData, -} from '@nymproject/types'; +import { DecCoin, Gateway, MixNode, MixNodeCostParams, PledgeData } from '@nymproject/types'; import { Fee } from '@nymproject/types/dist/types/rust/Fee'; import { TBondedGateway, TBondedMixnode } from 'src/context'; @@ -52,7 +44,6 @@ export type TBondMixNodeArgs = { export type TBondMoreArgs = { additionalPledge: DecCoin; fee?: Fee; - }; export type TNodeDescription = { diff --git a/nym-wallet/src/types/rust/AppEnv.ts b/nym-wallet/src/types/rust/AppEnv.ts index 3f47419469..861ecd9ef5 100644 --- a/nym-wallet/src/types/rust/AppEnv.ts +++ b/nym-wallet/src/types/rust/AppEnv.ts @@ -1,2 +1,5 @@ - -export interface AppEnv { ADMIN_ADDRESS: string | null, SHOW_TERMINAL: string | null, ENABLE_QA_MODE: string | null, } \ No newline at end of file +export interface AppEnv { + ADMIN_ADDRESS: string | null; + SHOW_TERMINAL: string | null; + ENABLE_QA_MODE: string | null; +} diff --git a/nym-wallet/src/types/rust/Epoch.ts b/nym-wallet/src/types/rust/Epoch.ts index b9529dc336..21ec4ac2df 100644 --- a/nym-wallet/src/types/rust/Epoch.ts +++ b/nym-wallet/src/types/rust/Epoch.ts @@ -1,3 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export interface Epoch { id: number, start: bigint, end: bigint, duration_seconds: bigint, } \ No newline at end of file +export interface Epoch { + id: number; + start: bigint; + end: bigint; + duration_seconds: bigint; +} diff --git a/nym-wallet/src/types/rust/Network.ts b/nym-wallet/src/types/rust/Network.ts index fd31bccafa..f8615cd992 100644 --- a/nym-wallet/src/types/rust/Network.ts +++ b/nym-wallet/src/types/rust/Network.ts @@ -1,2 +1 @@ - -export type Network = "QA" | "SANDBOX" | "MAINNET"; \ No newline at end of file +export type Network = 'QA' | 'SANDBOX' | 'MAINNET'; diff --git a/nym-wallet/src/types/rust/StateParams.ts b/nym-wallet/src/types/rust/StateParams.ts index 85c362a3b2..a394fb0d42 100644 --- a/nym-wallet/src/types/rust/StateParams.ts +++ b/nym-wallet/src/types/rust/StateParams.ts @@ -1,3 +1,7 @@ -import type { DecCoin } from "../../../../ts-packages/types/src/types/rust/DecCoin"; +import type { DecCoin } from '@nymproject/types'; -export interface TauriContractStateParams { minimum_mixnode_pledge: DecCoin, minimum_gateway_pledge: DecCoin, minimum_mixnode_delegation: DecCoin | null, } \ No newline at end of file +export interface TauriContractStateParams { + minimum_mixnode_pledge: DecCoin; + minimum_gateway_pledge: DecCoin; + minimum_mixnode_delegation: DecCoin | null; +} diff --git a/nym-wallet/src/types/rust/ValidatorUrl.ts b/nym-wallet/src/types/rust/ValidatorUrl.ts index 9c74679bac..801e045a00 100644 --- a/nym-wallet/src/types/rust/ValidatorUrl.ts +++ b/nym-wallet/src/types/rust/ValidatorUrl.ts @@ -1,2 +1,4 @@ - -export interface ValidatorUrl { url: string, name: string | null, } \ No newline at end of file +export interface ValidatorUrl { + url: string; + name: string | null; +} diff --git a/nym-wallet/src/types/rust/ValidatorUrls.ts b/nym-wallet/src/types/rust/ValidatorUrls.ts index 3a104150b1..98ca799fb5 100644 --- a/nym-wallet/src/types/rust/ValidatorUrls.ts +++ b/nym-wallet/src/types/rust/ValidatorUrls.ts @@ -1,3 +1,5 @@ -import type { ValidatorUrl } from "./ValidatorUrl"; +import type { ValidatorUrl } from './ValidatorUrl'; -export interface ValidatorUrls { urls: Array<ValidatorUrl>, } \ No newline at end of file +export interface ValidatorUrls { + urls: Array<ValidatorUrl>; +} diff --git a/nym-wallet/src/typings/FC.d.ts b/nym-wallet/src/typings/FC.d.ts new file mode 100644 index 0000000000..08ebdfa298 --- /dev/null +++ b/nym-wallet/src/typings/FC.d.ts @@ -0,0 +1 @@ +declare type FCWithChildren<P = {}> = React.FC<React.PropsWithChildren<P>>; diff --git a/nym-wallet/src/utils/index.ts b/nym-wallet/src/utils/index.ts index 49d56c6f72..704fe9b80b 100644 --- a/nym-wallet/src/utils/index.ts +++ b/nym-wallet/src/utils/index.ts @@ -2,9 +2,16 @@ import { appWindow } from '@tauri-apps/api/window'; import bs58 from 'bs58'; import Big from 'big.js'; import { valid } from 'semver'; -import { isValidRawCoin, DecCoin, MixNodeCostParams } from '@nymproject/types'; +import { add, format, fromUnixTime } from 'date-fns'; +import { DecCoin, isValidRawCoin, MixNodeCostParams } from '@nymproject/types'; import { TPoolOption } from 'src/components'; -import { getDefaultMixnodeCostParams, getLockedCoins, getSpendableCoins, userBalance } from '../requests'; +import { + getCurrentInterval, + getDefaultMixnodeCostParams, + getLockedCoins, + getSpendableCoins, + userBalance, +} from '../requests'; import { Console } from './console'; export const validateKey = (key: string, bytesLength: number): boolean => { @@ -198,3 +205,25 @@ export const unymToNym = (unym: string | Big, dp = 4) => { } return nym; }; + +export const getIntervalAsDate = async () => { + const interval = await getCurrentInterval(); + const secondsToNextInterval = + Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds); + + const nextInterval = format( + add(new Date(), { + seconds: secondsToNextInterval, + }), + 'dd/MM/yyyy, HH:mm', + ); + + const nextEpoch = format( + add(fromUnixTime(Number(interval.current_epoch_start_unix)), { + seconds: Number(interval.epoch_length_seconds), + }), + 'HH:mm', + ); + + return { nextEpoch, nextInterval }; +}; diff --git a/nym-wallet/webdriver/yarn.lock b/nym-wallet/webdriver/yarn.lock index 41f18e8bd4..0c01bfb5cc 100644 --- a/nym-wallet/webdriver/yarn.lock +++ b/nym-wallet/webdriver/yarn.lock @@ -1312,9 +1312,9 @@ hosted-git-info@^2.1.4: integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== http-cache-semantics@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== http2-wrapper@^1.0.0-beta.5.2: version "1.0.3" @@ -2471,9 +2471,9 @@ type-fest@^0.21.3: integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== ua-parser-js@^0.7.21: - version "0.7.28" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" - integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== + version "0.7.33" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" + integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw== unbzip2-stream@1.3.3: version "1.3.3" diff --git a/package.json b/package.json index bab720c114..3bd1e0c24d 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "private": true, "license": "Apache 2.0", "workspaces": [ - "sdk/typescript/**", + "sdk/typescript/packages/**", "ts-packages/*", "nym-wallet", - "nym-connect", + "nym-connect/**", "explorer", "types", "clients/validator" @@ -25,6 +25,7 @@ "build:ci:collect-artifacts": "mkdir -p ts-packages/dist && mv ts-packages/react-components/storybook-static ts-packages/dist/storybook && mv ts-packages/react-webpack-with-theme-example/dist ts-packages/dist/example", "lint": "lerna run lint --stream", "lint:fix": "lerna run lint:fix --stream", + "tsc": "lerna run tsc --stream", "types:lint:fix": "lerna run lint:fix --scope @nymproject/types --scope @nymproject/nym-wallet-app", "audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run" }, diff --git a/sdk/README.md b/sdk/README.md index 62e53144b2..4570ace5cf 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -5,8 +5,9 @@ Welcome to the Nym SDK. This is a good starting point for finding resources to b The SDK is split into technologies and platforms: - [Typescript](typescript) - packages for the Javascript ecosystem leveraging Nym's Typescript and WASM clients. Use these to build browser apps, web apps and mobile apps that can make use of the Nym mixnet and Coconut credentials +- [Rust](rust) - crates for Nym platform support to Rust application and libraries. Coming soon: - [iOS](ios) - native libraries for iOS, that can be used directly or from React Native using wrappers -- [Android](android) - native libraries for iOS, that can be used via JNI or from React Native using wrappers \ No newline at end of file +- [Android](android) - native libraries for iOS, that can be used via JNI or from React Native using wrappers diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml new file mode 100644 index 0000000000..32f98097ca --- /dev/null +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "nym-sdk" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +client-core = { path = "../../../clients/client-core", features = ["fs-surb-storage"]} +nym-crypto = { path = "../../../common/crypto" } +gateway-client = { path = "../../../common/client-libs/gateway-client" } +gateway-requests = { path = "../../../gateway/gateway-requests" } +nym-network-defaults = { path = "../../../common/network-defaults" } +nym-sphinx = { path = "../../../common/nymsphinx" } +nym-task = { path = "../../../common/task" } +nym-topology = { path = "../../../common/topology" } +validator-client = { path = "../../../common/client-libs/validator-client", features = ["nyxd-client"] } + +futures = "0.3" +log = { workspace = true } +rand = { version = "0.7.3" } +tap = "1.0.1" +thiserror = "1.0.38" +url = "2.2" +toml = "0.5.10" + +[dev-dependencies] +pretty_env_logger = "0.4.0" +tokio = { version = "1", features = ["full"] } +nym-bin-common = { path = "../../../common/bin-common" } diff --git a/sdk/rust/nym-sdk/examples/builder.rs b/sdk/rust/nym-sdk/examples/builder.rs new file mode 100644 index 0000000000..87e6461418 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/builder.rs @@ -0,0 +1,33 @@ +use nym_sdk::mixnet; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + // Create client builder, including ephemeral keys. The builder can be usable in the context + // where you don't want to connect just yet. + // Since not storage paths are given, the surb storage will be inactive. + let client = mixnet::MixnetClientBuilder::new() + .build::<mixnet::EmptyReplyStorage>() + .await + .unwrap(); + + // Now we connect to the mixnet, using ephemeral keys already created + let mut client = client.connect_to_mixnet().await.unwrap(); + + // Be able to get our client address + let our_address = client.nym_address(); + println!("Our client nym address is: {our_address}"); + + // Send a message throught the mixnet to ourselves + client.send_str(*our_address, "hello there").await; + + println!("Waiting for message"); + if let Some(received) = client.wait_for_messages().await { + for r in received { + println!("Received: {}", String::from_utf8_lossy(&r.message)); + } + } + + client.disconnect().await; +} diff --git a/sdk/rust/nym-sdk/examples/builder_with_storage.rs b/sdk/rust/nym-sdk/examples/builder_with_storage.rs new file mode 100644 index 0000000000..c6ff57627b --- /dev/null +++ b/sdk/rust/nym-sdk/examples/builder_with_storage.rs @@ -0,0 +1,39 @@ +use std::path::PathBuf; + +use nym_sdk::mixnet; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + // Specify some config options + let config_dir = PathBuf::from("/tmp/mixnet-client"); + let storage_paths = + mixnet::StoragePaths::new_from_dir(mixnet::KeyMode::Keep, &config_dir).unwrap(); + + // Create the client with a storage backend, and enable it by giving it some paths + let client = mixnet::MixnetClientBuilder::new() + .enable_storage(storage_paths) + .build::<mixnet::ReplyStorage>() + .await + .unwrap(); + + // Now we connect to the mixnet, using ephemeral keys already created + let mut client = client.connect_to_mixnet().await.unwrap(); + + // Be able to get our client address + let our_address = client.nym_address(); + println!("Our client nym address is: {our_address}"); + + // Send a message throught the mixnet to ourselves + client.send_str(*our_address, "hello there").await; + + println!("Waiting for message"); + if let Some(received) = client.wait_for_messages().await { + for r in received { + println!("Received: {}", String::from_utf8_lossy(&r.message)); + } + } + + client.disconnect().await; +} diff --git a/sdk/rust/nym-sdk/examples/custom_topology_provider.rs b/sdk/rust/nym-sdk/examples/custom_topology_provider.rs new file mode 100644 index 0000000000..59da44e711 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/custom_topology_provider.rs @@ -0,0 +1,78 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use nym_sdk::mixnet; +use nym_topology::provider_trait::{async_trait, TopologyProvider}; +use nym_topology::{nym_topology_from_detailed, NymTopology}; +use url::Url; + +struct MyTopologyProvider { + validator_client: validator_client::client::NymApiClient, +} + +impl MyTopologyProvider { + fn new(nym_api_url: Url) -> MyTopologyProvider { + MyTopologyProvider { + validator_client: validator_client::client::NymApiClient::new(nym_api_url), + } + } + + async fn get_topology(&self) -> NymTopology { + let mixnodes = self + .validator_client + .get_cached_active_mixnodes() + .await + .unwrap(); + + // in our topology provider only use mixnodes that have mix_id divisible by 3 + // and have more than 100k nym (i.e. 100'000'000'000 unym) in stake + // why? because this is just an example to showcase arbitrary uses and capabilities of this trait + let filtered_mixnodes = mixnodes + .into_iter() + .filter(|mix| { + mix.mix_id() % 3 == 0 && mix.total_stake() > "100000000000".parse().unwrap() + }) + .collect::<Vec<_>>(); + + let gateways = self.validator_client.get_cached_gateways().await.unwrap(); + + nym_topology_from_detailed(filtered_mixnodes, gateways) + } +} + +#[async_trait] +impl TopologyProvider for MyTopologyProvider { + // this will be manually refreshed on a timer specified inside mixnet client config + async fn get_new_topology(&mut self) -> Option<NymTopology> { + Some(self.get_topology().await) + } +} + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + let nym_api = "https://validator.nymtech.net/api/".parse().unwrap(); + let my_topology_provider = MyTopologyProvider::new(nym_api); + + // Passing no config makes the client fire up an ephemeral session and figure shit out on its own + let mut client = mixnet::MixnetClientBuilder::new() + .custom_topology_provider(Box::new(my_topology_provider)) + .build::<mixnet::EmptyReplyStorage>() + .await + .unwrap() + .connect_to_mixnet() + .await + .unwrap(); + + let our_address = client.nym_address(); + println!("Our client nym address is: {our_address}"); + + // Send a message through the mixnet to ourselves + client.send_str(*our_address, "hello there").await; + + println!("Waiting for message (ctrl-c to exit)"); + client + .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message))) + .await; +} diff --git a/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs b/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs new file mode 100644 index 0000000000..8c40e86870 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs @@ -0,0 +1,85 @@ +use nym_sdk::mixnet; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + let user_chosen_gateway_id = None; + let nym_api_endpoints = vec!["https://validator.nymtech.net/api/".parse().unwrap()]; + let config = mixnet::Config::new(user_chosen_gateway_id, nym_api_endpoints); + + // Just some plain data to pretend we have some external storage that the application + // implementer is using. + let mut mock_storage = MockStorage::empty(); + + let first_run = true; + + let client = if first_run { + // Create a client without a storage backend + let mut client = mixnet::MixnetClientBuilder::new() + .config(config) + .build::<mixnet::EmptyReplyStorage>() + .await + .unwrap(); + + // In this we want to provide our own gateway config struct, and handle persisting this info to disk + // ourselves (e.g., as part of our own configuration file). + client.register_and_authenticate_gateway().await.unwrap(); + mock_storage.write(client.get_keys(), client.get_gateway_endpoint().unwrap()); + client + } else { + let (keys, gateway_config) = mock_storage.read(); + + // Create a client without a storage backend, but with explicitly set keys and gateway + // configuration. This creates the client in a registered state. + mixnet::MixnetClientBuilder::new() + .config(config) + .keys(keys) + .gateway_config(gateway_config) + .build::<mixnet::EmptyReplyStorage>() + .await + .unwrap() + }; + + // Connect to the mixnet, now we're listening for incoming + let mut client = client.connect_to_mixnet().await.unwrap(); + + // Be able to get our client address + let our_address = client.nym_address(); + println!("Our client nym address is: {our_address}"); + + // Send important info up the pipe to a buddy + client.send_str(*our_address, "hello there").await; + + println!("Waiting for message"); + if let Some(received) = client.wait_for_messages().await { + for r in received { + println!("Received: {}", String::from_utf8_lossy(&r.message)); + } + } + + client.disconnect().await; +} + +#[allow(unused)] +struct MockStorage { + pub gateway_config: Option<mixnet::GatewayEndpointConfig>, + pub keys: Option<Vec<u8>>, +} + +impl MockStorage { + fn read(&self) -> (mixnet::Keys, mixnet::GatewayEndpointConfig) { + todo!(); + } + + fn write(&mut self, _keys: mixnet::KeysArc, _gateway_config: &mixnet::GatewayEndpointConfig) { + log::info!("todo"); + } + + fn empty() -> Self { + Self { + gateway_config: None, + keys: None, + } + } +} diff --git a/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs b/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs new file mode 100644 index 0000000000..0c951c736b --- /dev/null +++ b/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs @@ -0,0 +1,90 @@ +// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> +// SPDX-License-Identifier: Apache-2.0 + +use nym_sdk::mixnet; +use nym_topology::mix::Layer; +use nym_topology::{mix, NymTopology}; +use std::collections::HashMap; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + // Passing no config makes the client fire up an ephemeral session and figure shit out on its own + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + let starting_topology = client.read_current_topology().await.unwrap(); + + // but we don't like our default topology, we want to use only those very specific, hardcoded, nodes: + let mut mixnodes = HashMap::new(); + mixnodes.insert( + 1, + vec![mix::Node { + mix_id: 63, + owner: "n1k52k5n45cqt5qpjh8tcwmgqm0wkt355yy0g5vu".to_string(), + host: "172.105.92.48".parse().unwrap(), + mix_host: "172.105.92.48:1789".parse().unwrap(), + identity_key: "GLdR2NRVZBiCoCbv4fNqt9wUJZAnNjGXHkx3TjVAUzrK" + .parse() + .unwrap(), + sphinx_key: "CBmYewWf43iarBq349KhbfYMc9ys2ebXWd4Vp4CLQ5Rq" + .parse() + .unwrap(), + layer: Layer::One, + version: "1.1.0".to_string(), + }], + ); + mixnodes.insert( + 2, + vec![mix::Node { + mix_id: 23, + owner: "n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47".to_string(), + host: "178.79.143.65".parse().unwrap(), + mix_host: "178.79.143.65:1789".parse().unwrap(), + identity_key: "4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz" + .parse() + .unwrap(), + sphinx_key: "8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG" + .parse() + .unwrap(), + layer: Layer::Two, + version: "1.1.0".to_string(), + }], + ); + mixnodes.insert( + 3, + vec![mix::Node { + mix_id: 66, + owner: "n1ae2pjd7q9p0dea65pqkvcm4x9s264v4fktpyru".to_string(), + host: "139.162.247.97".parse().unwrap(), + mix_host: "139.162.247.97:1789".parse().unwrap(), + identity_key: "66UngapebhJRni3Nj52EW1qcNsWYiuonjkWJzHFsmyYY" + .parse() + .unwrap(), + sphinx_key: "7KyZh8Z8KxuVunqytAJ2eXFuZkCS7BLTZSzujHJZsGa2" + .parse() + .unwrap(), + layer: Layer::Three, + version: "1.1.0".to_string(), + }], + ); + + // but we like the available gateways, so keep using them! + // (we like them because the author of this example is too lazy to use the same hardcoded gateway + // during client initialisation to make sure we are able to send to ourselves : ) ) + let custom_topology = NymTopology::new(mixnodes, starting_topology.gateways().to_vec()); + + client.manually_overwrite_topology(custom_topology).await; + + // and everything we send now should only ever go via those nodes + + let our_address = client.nym_address(); + println!("Our client nym address is: {our_address}"); + + // Send a message through the mixnet to ourselves + client.send_str(*our_address, "hello there").await; + + println!("Waiting for message (ctrl-c to exit)"); + client + .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message))) + .await; +} diff --git a/sdk/rust/nym-sdk/examples/simple.rs b/sdk/rust/nym-sdk/examples/simple.rs new file mode 100644 index 0000000000..f2325e6048 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/simple.rs @@ -0,0 +1,21 @@ +use nym_sdk::mixnet; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_logging(); + + // Passing no config makes the client fire up an ephemeral session and figure shit out on its own + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + + // Be able to get our client address + let our_address = client.nym_address(); + println!("Our client nym address is: {our_address}"); + + // Send a message throught the mixnet to ourselves + client.send_str(*our_address, "hello there").await; + + println!("Waiting for message (ctrl-c to exit)"); + client + .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message))) + .await; +} diff --git a/sdk/rust/nym-sdk/src/error.rs b/sdk/rust/nym-sdk/src/error.rs new file mode 100644 index 0000000000..75c555c947 --- /dev/null +++ b/sdk/rust/nym-sdk/src/error.rs @@ -0,0 +1,43 @@ +use std::path::PathBuf; + +/// Top-level Error enum for the mixnet client and its relevant types. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("i/o error: {0}")] + IoError(#[from] std::io::Error), + #[error("toml serialization error: {0}")] + TomlSerializationError(#[from] toml::ser::Error), + #[error("toml deserialization error: {0}")] + TomlDeserializationError(#[from] toml::de::Error), + #[error("Ed25519 error: {0}")] + Ed25519RecoveryError(#[from] nym_crypto::asymmetric::identity::Ed25519RecoveryError), + + #[error(transparent)] + ClientCoreError(#[from] client_core::error::ClientCoreError), + + #[error("key file encountered that we don't want to overwrite: {0}")] + DontOverwrite(PathBuf), + #[error("shared gateway key file encountered that we don't want to overwrite: {0}")] + DontOverwriteGatewayKey(PathBuf), + #[error("no gateway config available for writing")] + GatewayNotAvailableForWriting, + #[error("expected to received a directory, received: {0}")] + ExpectedDirectory(PathBuf), + + #[error("failed to transition to registered state before connection to mixnet")] + FailedToTransitionToRegisteredState, + #[error( + "registering with gateway when the client is already in a registered state is not \ + supported, and likely and user mistake" + )] + ReregisteringGatewayNotSupported, + #[error("no gateway key set")] + NoGatewayKeySet, + + #[error("failed to create reply storage backend: {source}")] + StorageError { + source: Box<dyn std::error::Error + Send + Sync>, + }, +} + +pub type Result<T, E = Error> = std::result::Result<T, E>; diff --git a/sdk/rust/nym-sdk/src/lib.rs b/sdk/rust/nym-sdk/src/lib.rs new file mode 100644 index 0000000000..d4306ab336 --- /dev/null +++ b/sdk/rust/nym-sdk/src/lib.rs @@ -0,0 +1,9 @@ +//! Rust SDK for the Nym platform +//! +//! The main component currently is [`mixnet`]. + +mod error; + +pub mod mixnet; + +pub use error::{Error, Result}; diff --git a/sdk/rust/nym-sdk/src/mixnet.rs b/sdk/rust/nym-sdk/src/mixnet.rs new file mode 100644 index 0000000000..c2a6c42108 --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet.rs @@ -0,0 +1,56 @@ +//! The mixnet component of the Rust SDK for the Nym platform +//! +//! +//! # Basic example +//! +//! ```no_run +//! use nym_sdk::mixnet; +//! +//! #[tokio::main] +//! async fn main() { +//! // Passing no config makes the client fire up an ephemeral session and figure stuff out on +//! // its own +//! let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); +//! +//! // Be able to get our client address +//! let our_address = client.nym_address(); +//! println!("Our client nym address is: {our_address}"); +//! +//! // Send a message throught the mixnet to ourselves +//! client.send_str(*our_address, "hello there").await; +//! +//! println!("Waiting for message"); +//! if let Some(received) = client.wait_for_messages().await { +//! for r in received { +//! println!("Received: {}", String::from_utf8_lossy(&r.message)); +//! } +//! } +//! +//! client.disconnect().await; +//! } +//! ``` + +mod client; +mod config; +mod connection_state; +mod keys; +mod paths; + +pub use client::{ + DisconnectedMixnetClient, IncludedSurbs, MixnetClient, MixnetClientBuilder, MixnetClientSender, +}; +pub use client_core::{ + client::{ + inbound_messages::InputMessage, + replies::reply_storage::{fs_backend::Backend as ReplyStorage, Empty as EmptyReplyStorage}, + }, + config::GatewayEndpointConfig, +}; +pub use config::Config; +pub use keys::{Keys, KeysArc}; +pub use nym_sphinx::{ + addressing::clients::{ClientIdentity, Recipient}, + receiver::ReconstructedMessage, +}; +pub use nym_topology::{provider_trait::TopologyProvider, NymTopology}; +pub use paths::{GatewayKeyMode, KeyMode, StoragePaths}; diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs new file mode 100644 index 0000000000..7b754d8af2 --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -0,0 +1,665 @@ +use std::{path::Path, sync::Arc}; + +use client_core::{ + client::{ + base_client::{ + BaseClientBuilder, ClientInput, ClientOutput, ClientState, CredentialsToggle, + }, + inbound_messages::InputMessage, + key_manager::KeyManager, + received_buffer::ReconstructedMessagesReceiver, + replies::reply_storage::ReplyStorageBackend, + }, + config::{persistence::key_pathfinder::ClientKeyPathfinder, GatewayEndpointConfig}, +}; +use nym_crypto::asymmetric::identity; +use nym_sphinx::{ + addressing::clients::{ClientIdentity, Recipient}, + receiver::ReconstructedMessage, +}; +use nym_task::{ + connections::{ConnectionCommandSender, LaneQueueLengths, TransmissionLane}, + TaskManager, +}; + +use futures::StreamExt; +use nym_topology::provider_trait::TopologyProvider; +use nym_topology::NymTopology; +use validator_client::nyxd::SigningNyxdClient; + +use crate::{Error, Result}; + +use super::{connection_state::BuilderState, Config, GatewayKeyMode, Keys, KeysArc, StoragePaths}; + +// The number of surbs to include in a message by default +const DEFAULT_NUMBER_OF_SURBS: u32 = 5; + +#[derive(Default)] +pub struct MixnetClientBuilder { + config: Option<Config>, + storage_paths: Option<StoragePaths>, + keys: Option<Keys>, + gateway_config: Option<GatewayEndpointConfig>, + custom_topology_provider: Option<Box<dyn TopologyProvider>>, +} + +impl MixnetClientBuilder { + pub fn new() -> Self { + Self::default() + } + + #[must_use] + pub fn config(mut self, config: Config) -> Self { + self.config = Some(config); + self + } + + /// Enabled storage + #[must_use] + pub fn enable_storage(mut self, paths: StoragePaths) -> Self { + self.storage_paths = Some(paths); + self + } + + #[must_use] + pub fn keys(mut self, keys: Keys) -> Self { + self.keys = Some(keys); + self + } + + #[must_use] + pub fn gateway_config(mut self, gateway_config: GatewayEndpointConfig) -> Self { + self.gateway_config = Some(gateway_config); + self + } + + #[must_use] + pub fn custom_topology_provider( + mut self, + topology_provider: Box<dyn TopologyProvider>, + ) -> Self { + self.custom_topology_provider = Some(topology_provider); + self + } + + /// Construct a [`DisconnectedMixnetClient`] from the setup specified. + pub async fn build<B>(self) -> Result<DisconnectedMixnetClient<B>> + where + B: ReplyStorageBackend + Send + Sync + 'static, + <B as ReplyStorageBackend>::StorageError: Send + Sync, + { + let config = self.config.unwrap_or_default(); + let storage_paths = self.storage_paths; + + let mut client = DisconnectedMixnetClient::new( + Some(config), + storage_paths, + self.custom_topology_provider, + ) + .await?; + + if let Some(keys) = self.keys { + client.set_keys(keys); + } + + // If we have a gateway config, we can move the client into a registered state. This will + // fail if no gateway key is set. + if let Some(gateway_config) = self.gateway_config { + client.register_gateway_with_config(gateway_config)?; + } + + Ok(client) + } +} + +/// Represents a client that is not yet connected to the mixnet. You typically create one when you +/// want to have a separate configuration and connection phase. Once the mixnet client builder is +/// configured, call [`MixnetClientBuilder::connect_to_mixnet()`] to transition to a connected +/// client. +pub struct DisconnectedMixnetClient<B> +where + B: ReplyStorageBackend + Sync + Send + 'static, +{ + /// Keys handled by the client + key_manager: KeyManager, + + /// Client configuration + config: Config, + + /// Paths for client keys, including identity, encryption, ack and shared gateway keys. + storage_paths: Option<StoragePaths>, + + /// The client can be in one of multiple states, depending on how it is created and if it's + /// connected to the mixnet. + state: BuilderState, + + /// The storage backend for reply-SURBs + reply_storage_backend: B, + + /// Alternative provider of network topology used for constructing sphinx packets. + custom_topology_provider: Option<Box<dyn TopologyProvider>>, +} + +impl<B> DisconnectedMixnetClient<B> +where + B: ReplyStorageBackend + Sync + Send + 'static, +{ + /// Create a new mixnet client in a disconnected state. If no config options are supplied, + /// creates a new client with ephemeral keys stored in RAM, which will be discarded at + /// application close. + /// + /// Callers have the option of supplying futher parameters to store persistent identities at a + /// location on-disk, if desired. + async fn new( + config: Option<Config>, + paths: Option<StoragePaths>, + custom_topology_provider: Option<Box<dyn TopologyProvider>>, + ) -> Result<DisconnectedMixnetClient<B>> + where + <B as ReplyStorageBackend>::StorageError: Send + Sync, + { + let config = config.unwrap_or_default(); + let reply_surb_database_path = paths.as_ref().map(|p| p.reply_surb_database_path.clone()); + + // The reply storage backend is generic, and can be set by the caller/instantiator + let reply_storage_backend = B::new(&config.debug_config, reply_surb_database_path) + .await + .map_err(|err| Error::StorageError { + source: Box::new(err), + })?; + + // If we are provided paths to keys, use them if they are available. And if they are + // not, write the generated keys back to storage. + let key_manager = if let Some(ref paths) = paths { + let path_finder = ClientKeyPathfinder::from(paths.clone()); + + // Try load keys + match KeyManager::load_keys_but_gateway_is_optional(&path_finder) { + Ok(key_manager) => { + log::debug!("Keys loaded"); + key_manager + } + Err(err) => { + log::debug!("Not loading keys: {err}"); + if let Some(path) = path_finder.any_file_exists_and_return() { + if paths.operating_mode.is_keep() { + return Err(Error::DontOverwrite(path)); + } + } + + // Double check using a function that has slightly different internal logic. I + // know this is a bit defensive, but I don't want to overwrite + assert!(!(path_finder.any_file_exists() && paths.operating_mode.is_keep())); + + // Create new keys and write to storage + let key_manager = client_core::init::new_client_keys(); + // WARN: this will overwrite! + key_manager.store_keys(&path_finder)?; + key_manager + } + } + } else { + // Ephemeral keys that we only store in memory + log::debug!("Creating new ephemeral keys"); + client_core::init::new_client_keys() + }; + + Ok(DisconnectedMixnetClient { + key_manager, + config, + storage_paths: paths, + state: BuilderState::New, + reply_storage_backend, + custom_topology_provider, + }) + } + + /// Client keys are generated at client creation if none were found. The gateway shared + /// key, however, is created during the gateway registration handshake so it might not + /// necessarily be available. + fn has_gateway_key(&self) -> bool { + self.key_manager.is_gateway_key_set() + } + + /// Sets the keys of this [`MixnetClientBuilder<B>`]. + fn set_keys(&mut self, keys: Keys) { + self.key_manager.set_identity_keypair(keys.identity_keypair); + self.key_manager + .set_encryption_keypair(keys.encryption_keypair); + self.key_manager.set_ack_key(keys.ack_key); + + self.key_manager + .insert_gateway_shared_key(Arc::new(keys.gateway_shared_key)); + } + + /// Returns the keys of this [`DisconnectedMixnetClient<B>`]. Client keys are always available + /// since if none are specified at creation time, new random ones are generated. + pub fn get_keys(&self) -> KeysArc { + KeysArc::from(&self.key_manager) + } + + /// Sets the gateway endpoint of this [`MixnetClientBuilder<B>`]. + /// + /// NOTE: this will mark this builder as `Registered`, and the it is assumed that the keys are + /// also explicitly set. + pub fn register_gateway_with_config( + &mut self, + gateway_endpoint_config: GatewayEndpointConfig, + ) -> Result<()> { + if !self.has_gateway_key() { + return Err(Error::NoGatewayKeySet); + } + + self.state = BuilderState::Registered { + gateway_endpoint_config, + }; + + Ok(()) + } + + /// Register with a gateway. If a gateway is provided in the config then that will try to be + /// used. If none is specified, a gateway at random will be picked. + /// + /// # Errors + /// + /// This function will return an error if you try to re-register when in an already registered + /// state. + pub async fn register_and_authenticate_gateway(&mut self) -> Result<()> { + if self.state != BuilderState::New { + return Err(Error::ReregisteringGatewayNotSupported); + } + log::debug!("Registering with gateway"); + + let user_chosen_gateway = self + .config + .user_chosen_gateway + .as_ref() + .map(identity::PublicKey::from_base58_string) + .transpose()?; + + let gateway_config = client_core::init::register_with_gateway( + &mut self.key_manager, + self.config.nym_api_endpoints.clone(), + user_chosen_gateway, + // TODO: this should probably be configurable with the config + false, + ) + .await?; + + self.state = BuilderState::Registered { + gateway_endpoint_config: gateway_config, + }; + Ok(()) + } + + /// Returns the get gateway endpoint of this [`MixnetClientBuilder<B>`]. + pub fn get_gateway_endpoint(&self) -> Option<&GatewayEndpointConfig> { + self.state.gateway_endpoint_config() + } + + fn write_gateway_key(&self, paths: StoragePaths, key_mode: &GatewayKeyMode) -> Result<()> { + let path_finder = ClientKeyPathfinder::from(paths); + if path_finder.gateway_key_file_exists() && key_mode.is_keep() { + return Err(Error::DontOverwriteGatewayKey( + path_finder.gateway_shared_key().to_path_buf(), + )); + }; + self.key_manager.store_gateway_key(&path_finder)?; + Ok(()) + } + + fn write_gateway_endpoint_config(&self, gateway_endpoint_config_path: &Path) -> Result<()> { + let gateway_endpoint_config = toml::to_string( + self.get_gateway_endpoint() + .ok_or(Error::GatewayNotAvailableForWriting)?, + )?; + + // Ensure the whole directory structure exists + if let Some(parent_dir) = gateway_endpoint_config_path.parent() { + std::fs::create_dir_all(parent_dir)?; + } + std::fs::write(gateway_endpoint_config_path, gateway_endpoint_config)?; + Ok(()) + } + + fn read_gateway_endpoint_config(&mut self, gateway_endpoint_config_path: &Path) -> Result<()> { + let gateway_endpoint_config: GatewayEndpointConfig = + std::fs::read_to_string(gateway_endpoint_config_path) + .map(|str| toml::from_str(&str))??; + + self.state = BuilderState::Registered { + gateway_endpoint_config, + }; + Ok(()) + } + + /// Connect the client to the mixnet. + /// + /// - If the client is already registered with a gateway, use that gateway. + /// - If no gateway is registered, but there is an existing configuration and key, use that. + /// - If no gateway is registered, and there is no pre-existing configuration or key, try to + /// register a new gateway. + /// + /// # Example + /// + /// ```no_run + /// use nym_sdk::mixnet; + /// + /// #[tokio::main] + /// async fn main() { + /// let client = mixnet::MixnetClientBuilder::new() + /// .build::<mixnet::EmptyReplyStorage>() + /// .await + /// .unwrap(); + /// let client = client.connect_to_mixnet().await.unwrap(); + /// } + /// ``` + pub async fn connect_to_mixnet(mut self) -> Result<MixnetClient> + where + <B as ReplyStorageBackend>::StorageError: Sync + Send, + { + // For some simple cases we can figure how to setup gateway without it having to have been + // called in advance. + if matches!(self.state, BuilderState::New) { + if let Some(paths) = &self.storage_paths { + let paths = paths.clone(); + if self.has_gateway_key() { + // If we have a gateway key from client, then we can just read the corresponding + // config + log::trace!("Gateway key found: loading"); + self.read_gateway_endpoint_config(&paths.gateway_endpoint_config)?; + } else { + // If we didn't find any shared gateway key during creation, that means we first + // need to register a gateway + log::trace!("Gateway key NOT found: registering new"); + self.register_and_authenticate_gateway().await?; + self.write_gateway_key(paths.clone(), &GatewayKeyMode::Overwrite)?; + self.write_gateway_endpoint_config(&paths.gateway_endpoint_config)?; + } + } else { + // If we don't have any key paths, just use ephemeral keys + self.register_and_authenticate_gateway().await?; + } + } + + // If the gateway is in a registered state, but without the gateway key set. + if matches!(self.state, BuilderState::Registered { .. }) && !self.has_gateway_key() { + return Err(Error::NoGatewayKeySet); + } + + // At this point we should be in a registered state, either at function entry or by the + // above convenience logic. + let BuilderState::Registered { gateway_endpoint_config } = self.state else { + return Err(Error::FailedToTransitionToRegisteredState); + }; + + let nym_address = + client_core::init::get_client_address(&self.key_manager, &gateway_endpoint_config); + + // TODO: we currently don't support having a bandwidth controller + let bandwidth_controller = None; + + let mut base_builder: BaseClientBuilder<'_, _, SigningNyxdClient> = BaseClientBuilder::new( + &gateway_endpoint_config, + &self.config.debug_config, + self.key_manager.clone(), + bandwidth_controller, + self.reply_storage_backend, + CredentialsToggle::Disabled, + self.config.nym_api_endpoints.clone(), + ); + + if let Some(topology_provider) = self.custom_topology_provider { + base_builder = base_builder.with_topology_provider(topology_provider); + } + + let mut started_client = base_builder.start_base().await?; + let client_input = started_client.client_input.register_producer(); + let mut client_output = started_client.client_output.register_consumer(); + let client_state = started_client.client_state; + + // Register our receiver + let reconstructed_receiver = client_output.register_receiver()?; + + Ok(MixnetClient { + nym_address, + key_manager: self.key_manager, + client_input, + client_output, + client_state, + reconstructed_receiver, + task_manager: started_client.task_manager, + }) + } +} + +pub enum IncludedSurbs { + Amount(u32), + ExposeSelfAddress, +} +impl Default for IncludedSurbs { + fn default() -> Self { + Self::Amount(DEFAULT_NUMBER_OF_SURBS) + } +} + +impl IncludedSurbs { + pub fn new(reply_surbs: u32) -> Self { + Self::Amount(reply_surbs) + } + + pub fn none() -> Self { + Self::Amount(0) + } + + pub fn expose_self_address() -> Self { + Self::ExposeSelfAddress + } +} + +/// Client connected to the Nym mixnet. +pub struct MixnetClient { + /// The nym address of this connected client. + nym_address: Recipient, + + /// Keys handled by the client + key_manager: KeyManager, + + /// Input to the client from the users perspective. This can be either data to send or controll + /// messages. + client_input: ClientInput, + + /// Output from the client from the users perspective. This is typically messages arriving from + /// the mixnet. + #[allow(dead_code)] + client_output: ClientOutput, + + /// The current state of the client that is exposed to the user. This includes things like + /// current message send queue length. + client_state: ClientState, + + /// A channel for messages arriving from the mixnet after they have been reconstructed. + reconstructed_receiver: ReconstructedMessagesReceiver, + + /// The task manager that controlls all the spawned tasks that the clients uses to do it's job. + task_manager: TaskManager, +} + +impl MixnetClient { + /// Create a new client and connect to the mixnet using ephemeral in-memory keys that are + /// discarded at application close. + /// + /// # Examples + /// + /// ```no_run + /// use nym_sdk::mixnet; + /// + /// #[tokio::main] + /// async fn main() { + /// let mut client = mixnet::MixnetClient::connect_new().await; + /// } + /// + /// ``` + pub async fn connect_new() -> Result<Self> { + MixnetClientBuilder::new() + .build::<crate::mixnet::EmptyReplyStorage>() + .await? + .connect_to_mixnet() + .await + } + + /// Get the client identity, which is the public key of the identity key pair. + pub fn identity(&self) -> ClientIdentity { + *self.key_manager.identity_keypair().public_key() + } + + /// Get the nym address for this client, if it is available. The nym address is composed of the + /// client identity, the client encryption key, and the gateway identity. + pub fn nym_address(&self) -> &Recipient { + &self.nym_address + } + + /// Get a shallow clone of [`MixnetClientSender`]. Useful if you want split the send and + /// receive logic in different locations. + pub fn sender(&self) -> MixnetClientSender { + MixnetClientSender { + client_input: self.client_input.clone(), + } + } + + /// Get a shallow clone of [`ConnectionCommandSender`]. This is useful if you want to e.g + /// explicitly close a transmission lane that is still sending data even though it should + /// cancel. + pub fn connection_command_sender(&self) -> ConnectionCommandSender { + self.client_input.connection_command_sender.clone() + } + + /// Get a shallow clone of [`LaneQueueLengths`]. This is useful to manually implement some form + /// of backpressure logic. + pub fn shared_lane_queue_lengths(&self) -> LaneQueueLengths { + self.client_state.shared_lane_queue_lengths.clone() + } + + /// Change the network topology used by this client for constructing sphinx packets into the + /// provided one. + pub async fn manually_overwrite_topology(&self, new_topology: NymTopology) { + self.client_state + .topology_accessor + .manually_change_topology(new_topology) + .await + } + + /// Gets the value of the currently used network topology. + pub async fn read_current_topology(&self) -> Option<NymTopology> { + self.client_state.topology_accessor.current_topology().await + } + + /// Restore default topology refreshing behaviour of this client. + pub fn restore_automatic_topology_refreshing(&self) { + self.client_state.topology_accessor.release_manual_control() + } + + /// Sends stringy data to the supplied Nym address + /// + /// # Example + /// + /// ```no_run + /// use nym_sdk::mixnet; + /// + /// #[tokio::main] + /// async fn main() { + /// let address = "foobar"; + /// let recipient = mixnet::Recipient::try_from_base58_string(address).unwrap(); + /// let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + /// client.send_str(recipient, "hi").await; + /// } + /// ``` + pub async fn send_str(&self, address: Recipient, message: &str) { + let message_bytes = message.to_string().into_bytes(); + self.send_bytes(address, message_bytes, IncludedSurbs::default()) + .await; + } + + /// Sends bytes to the supplied Nym address. There is the option to specify the number of + /// reply-SURBs to include. + /// + /// # Example + /// + /// ```no_run + /// use nym_sdk::mixnet; + /// + /// #[tokio::main] + /// async fn main() { + /// let address = "foobar"; + /// let recipient = mixnet::Recipient::try_from_base58_string(address).unwrap(); + /// let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + /// let surbs = mixnet::IncludedSurbs::default(); + /// client.send_bytes(recipient, "hi".to_owned().into_bytes(), surbs).await; + /// } + /// ``` + pub async fn send_bytes(&self, address: Recipient, message: Vec<u8>, surbs: IncludedSurbs) { + let lane = TransmissionLane::General; + let input_msg = match surbs { + IncludedSurbs::Amount(surbs) => { + InputMessage::new_anonymous(address, message, surbs, lane) + } + IncludedSurbs::ExposeSelfAddress => InputMessage::new_regular(address, message, lane), + }; + self.send(input_msg).await + } + + /// Sends a [`InputMessage`] to the mixnet. This is the most low-level sending function, for + /// full customization. + async fn send(&self, message: InputMessage) { + if self.client_input.send(message).await.is_err() { + log::error!("Failed to send message"); + } + } + + /// Sends a [`InputMessage`] to the mixnet. This is the most low-level sending function, for + /// full customization. + /// + /// Waits until the message is actually sent, or close to being sent, until returning. + /// + /// NOTE: this not yet implemented. + #[allow(unused)] + async fn send_wait(&self, _message: InputMessage) { + todo!(); + } + + /// Wait for messages from the mixnet + pub async fn wait_for_messages(&mut self) -> Option<Vec<ReconstructedMessage>> { + self.reconstructed_receiver.next().await + } + + /// Provide a callback to execute on incoming messages from the mixnet. + pub async fn on_messages<F>(&mut self, fun: F) + where + F: Fn(ReconstructedMessage), + { + while let Some(msgs) = self.wait_for_messages().await { + for msg in msgs { + fun(msg) + } + } + } + + /// Disconnect from the mixnet. Currently it is not supported to reconnect a disconnected + /// client. + pub async fn disconnect(&mut self) { + self.task_manager.signal_shutdown().ok(); + self.task_manager.wait_for_shutdown().await; + } +} + +pub struct MixnetClientSender { + client_input: ClientInput, +} + +impl MixnetClientSender { + pub async fn send_input_message(&mut self, message: InputMessage) { + if self.client_input.send(message).await.is_err() { + log::error!("Failed to send message"); + } + } +} diff --git a/sdk/rust/nym-sdk/src/mixnet/config.rs b/sdk/rust/nym-sdk/src/mixnet/config.rs new file mode 100644 index 0000000000..1f7dd49812 --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/config.rs @@ -0,0 +1,38 @@ +use client_core::config::DebugConfig; +use nym_network_defaults::mainnet; +use url::Url; + +/// Config struct for [`crate::mixnet::MixnetClient`] +pub struct Config { + /// If the user has explicitly specified a gateway. + pub user_chosen_gateway: Option<String>, + + /// List of nym-api endpoints + pub nym_api_endpoints: Vec<Url>, + + /// Flags controlling all sorts of internal client behaviour. + /// Changing these risk compromising network anonymity! + pub debug_config: DebugConfig, +} + +impl Default for Config { + fn default() -> Self { + let nym_api_endpoints = vec![mainnet::NYM_API.to_string().parse().unwrap()]; + Self { + user_chosen_gateway: Default::default(), + nym_api_endpoints, + debug_config: Default::default(), + } + } +} + +impl Config { + /// Creates a new [`Config`]. + pub fn new(user_chosen_gateway: Option<String>, nym_api_endpoints: Vec<Url>) -> Self { + Self { + user_chosen_gateway, + nym_api_endpoints, + debug_config: DebugConfig::default(), + } + } +} diff --git a/sdk/rust/nym-sdk/src/mixnet/connection_state.rs b/sdk/rust/nym-sdk/src/mixnet/connection_state.rs new file mode 100644 index 0000000000..611184298b --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/connection_state.rs @@ -0,0 +1,21 @@ +use client_core::config::GatewayEndpointConfig; + +#[derive(Debug, PartialEq, Eq)] +pub(super) enum BuilderState { + New, + Registered { + gateway_endpoint_config: GatewayEndpointConfig, + }, +} + +impl BuilderState { + pub(super) fn gateway_endpoint_config(&self) -> Option<&GatewayEndpointConfig> { + match self { + BuilderState::New => None, + BuilderState::Registered { + gateway_endpoint_config, + .. + } => Some(gateway_endpoint_config), + } + } +} diff --git a/sdk/rust/nym-sdk/src/mixnet/keys.rs b/sdk/rust/nym-sdk/src/mixnet/keys.rs new file mode 100644 index 0000000000..350c673f73 --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/keys.rs @@ -0,0 +1,68 @@ +use std::sync::Arc; + +use client_core::client::key_manager::KeyManager; +use gateway_requests::registration::handshake::SharedKeys; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_sphinx::acknowledgements::AckKey; + +/// The set of keys used by the client. Identity, encryption and ack keys are generated at creating +/// unless specified to loaded from storage or somehow explictly specified. The gateway shared key +/// is generated when registering with a gateway. +pub struct Keys { + /// The identity key of the client. + pub identity_keypair: identity::KeyPair, + /// The encryption key of the client. + pub encryption_keypair: encryption::KeyPair, + /// The ack key used by the client. + pub ack_key: AckKey, + + /// The gateway shared key that is obtained after registering with a gateway. + pub gateway_shared_key: SharedKeys, +} + +/// The set of keys used by the client, but where each key is stored in an [`std::sync::Arc`] for +/// easy cloning. +pub struct KeysArc { + /// The identity key of the client. + pub identity_keypair: Arc<identity::KeyPair>, + /// The encryption key of the client. + pub encryption_keypair: Arc<encryption::KeyPair>, + /// The ack key used by the client. + pub ack_key: Arc<AckKey>, + + /// The gateway shared key that is obtained after registering with a gateway. + pub gateway_shared_key: Arc<SharedKeys>, +} + +impl From<Keys> for KeyManager { + fn from(keys: Keys) -> Self { + KeyManager::from_keys( + keys.identity_keypair, + keys.encryption_keypair, + keys.gateway_shared_key, + keys.ack_key, + ) + } +} + +impl From<Keys> for KeysArc { + fn from(keys: Keys) -> Self { + KeysArc { + identity_keypair: keys.identity_keypair.into(), + encryption_keypair: keys.encryption_keypair.into(), + ack_key: keys.ack_key.into(), + gateway_shared_key: keys.gateway_shared_key.into(), + } + } +} + +impl From<&KeyManager> for KeysArc { + fn from(key_manager: &KeyManager) -> Self { + KeysArc { + identity_keypair: key_manager.identity_keypair(), + encryption_keypair: key_manager.encryption_keypair(), + ack_key: key_manager.ack_key(), + gateway_shared_key: key_manager.gateway_shared_key(), + } + } +} diff --git a/sdk/rust/nym-sdk/src/mixnet/paths.rs b/sdk/rust/nym-sdk/src/mixnet/paths.rs new file mode 100644 index 0000000000..dead111fdd --- /dev/null +++ b/sdk/rust/nym-sdk/src/mixnet/paths.rs @@ -0,0 +1,123 @@ +use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; +use std::path::{Path, PathBuf}; + +use crate::error::{Error, Result}; + +#[derive(Clone, Debug)] +pub enum KeyMode { + /// Use existing key files if they exists, otherwise create new ones. + Keep, + /// Create new keys, overwriting any potential previously existing keys. + Overwrite, +} + +impl KeyMode { + pub(crate) fn is_keep(&self) -> bool { + matches!(self, KeyMode::Keep) + } +} + +pub enum GatewayKeyMode { + /// Keep shared gateway key if found, otherwise create a new one. + Keep, + /// Create a new shared key and overwrite any potential existing one. + Overwrite, +} + +impl GatewayKeyMode { + pub(crate) fn is_keep(&self) -> bool { + matches!(self, GatewayKeyMode::Keep) + } +} + +/// Set of storage paths that the client will use if it is setup to persist keys, credentials, and +/// reply-SURBs. +#[derive(Clone, Debug)] +pub struct StoragePaths { + /// Determines how to handle existing key files found. + pub operating_mode: KeyMode, + + /// Client private identity key + pub private_identity: PathBuf, + /// Client public identity key + pub public_identity: PathBuf, + + /// Client private encryption key + pub private_encryption: PathBuf, + /// Client public encryption key + pub public_encryption: PathBuf, + + /// Key for handling acks + pub ack_key: PathBuf, + + /// Key setup after authenticating with a gateway + pub gateway_shared_key: PathBuf, + + /// The key isn't much use without knowing which entity it refers to. + pub gateway_endpoint_config: PathBuf, + + /// The database containing credentials + pub credential_database_path: PathBuf, + + /// The database storing reply surbs in-between sessions + pub reply_surb_database_path: PathBuf, +} + +impl StoragePaths { + /// Create a set of storage paths from a given directory. + /// + /// # Errors + /// + /// This function will return an error if it is passed a path to an existing file instead of a + /// directory. + pub fn new_from_dir(operating_mode: KeyMode, dir: &Path) -> Result<Self> { + if dir.is_file() { + return Err(Error::ExpectedDirectory(dir.to_owned())); + } + + Ok(Self { + // These filenames were chosen to match the ones we use in `nym-client`. Consider + // changing the defaults + operating_mode, + private_identity: dir.join("private_identity.pem"), + public_identity: dir.join("public_identity.pem"), + private_encryption: dir.join("private_encryption.pem"), + public_encryption: dir.join("public_encryption.pem"), + ack_key: dir.join("ack_key.pem"), + gateway_shared_key: dir.join("gateway_shared.pem"), + gateway_endpoint_config: dir.join("gateway_endpoint_config.toml"), + credential_database_path: dir.join("db.sqlite"), + reply_surb_database_path: dir.join("persistent_reply_store.sqlite"), + }) + } +} + +impl From<StoragePaths> for ClientKeyPathfinder { + fn from(paths: StoragePaths) -> Self { + Self { + identity_private_key: paths.private_identity, + identity_public_key: paths.public_identity, + encryption_private_key: paths.private_encryption, + encryption_public_key: paths.public_encryption, + gateway_shared_key: paths.gateway_shared_key, + ack_key: paths.ack_key, + } + } +} + +impl<T> From<&client_core::config::Config<T>> for StoragePaths { + fn from(value: &client_core::config::Config<T>) -> Self { + Self { + operating_mode: KeyMode::Keep, + private_identity: value.get_private_identity_key_file(), + public_identity: value.get_public_identity_key_file(), + private_encryption: value.get_private_encryption_key_file(), + public_encryption: value.get_public_encryption_key_file(), + ack_key: value.get_ack_key_file(), + gateway_shared_key: value.get_gateway_shared_key_file(), + gateway_endpoint_config: Default::default(), + credential_database_path: value.get_database_path(), + reply_surb_database_path: value.get_reply_surb_database_path(), + } + } +} diff --git a/sdk/typescript/.eslintrc.js b/sdk/typescript/.eslintrc.js new file mode 100644 index 0000000000..213b29beb1 --- /dev/null +++ b/sdk/typescript/.eslintrc.js @@ -0,0 +1,116 @@ +module.exports = { + root: true, + env: { + browser: true, + es6: true, + node: true, + jest: true, + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 2019, + sourceType: 'module', + project: './tsconfig.json', + }, + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + }, + plugins: ['react', 'react-hooks', 'jsx-a11y', 'prettier', 'jest'], + extends: [ + 'plugin:react/recommended', + 'airbnb', + 'airbnb-typescript', + 'prettier', + 'plugin:jest/recommended', + 'plugin:jest/style', + ], + ignorePatterns: ['dist/**/*', 'dist', 'node_modules'], + rules: { + 'jest/prefer-strict-equal': 'error', + 'jest/prefer-to-have-length': 'warn', + 'prettier/prettier': 'error', + 'import/prefer-default-export': 'off', + 'react/prop-types': 'off', + 'react/jsx-filename-extension': 'off', + 'react/jsx-props-no-spreading': 'off', + 'react/require-default-props': [ + 1, // warn + { + ignoreFunctionalComponents: true, + }, + ], + + // see https://github.com/facebook/create-react-app/discussions/11864#discussioncomment-1933829 + // and https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/function-component-definition.md + 'react/function-component-definition': [ + 2, // error + { + namedComponents: 'arrow-function', + unnamedComponents: 'arrow-function', + }, + ], + + 'import/no-extraneous-dependencies': [ + 2, // error + { + devDependencies: [ + '**/*.test.[jt]s', + '**/*.spec.[jt]s', + '**/*.test.[jt]sx', + '**/*.spec.[jt]sx', + // see https://github.com/storybookjs/linter-config/blob/master/eslint.config.js#L100 + '**/*.stories.*', + '**/.storybook/**/*.*', + ], + peerDependencies: true, + }, + ], + 'import/extensions': [ + 'error', + 'ignorePackages', + { + ts: 'never', + tsx: 'never', + js: 'never', + jsx: 'never', + }, + ], + '@typescript-eslint/no-use-before-define': [ + 'error', + { functions: false, classes: true, variables: true, typedefs: true }, + ], + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + 'no-use-before-define': [0], + 'import/no-unresolved': 0, + quotes: 'off', + '@typescript-eslint/quotes': [ + 2, + 'single', + { + avoidEscape: true, + }, + ], + '@typescript-eslint/no-unused-vars': [2, { argsIgnorePattern: '^_' }], + }, + // overrides: [ + // { + // files: ['**/*.js', '**/*.jsx'], + // parser: 'espree', // override parser to be the default eslint Javascript parser (see https://eslint.org/docs/user-guide/configuring/plugins#specifying-parser) + // extends: ['plugin:react/recommended', 'airbnb', 'prettier', 'plugin:jest/recommended', 'plugin:jest/style'], + // }, + // ], + settings: { + 'import/resolver': { + 'root-import': { + rootPathPrefix: '@', + rootPathSuffix: 'src', + extensions: ['.js', '.ts', '.tsx', '.jsx', '.mdx'], + }, + }, + }, +}; diff --git a/sdk/typescript/.eslintrc.json b/sdk/typescript/.eslintrc.json deleted file mode 100644 index d828c8e2e1..0000000000 --- a/sdk/typescript/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "root": true, - "extends": [ - "@nymproject/eslint-config-react-typescript" - ] -} diff --git a/sdk/typescript/examples/.gitignore b/sdk/typescript/examples/.gitignore new file mode 100644 index 0000000000..a546c54853 --- /dev/null +++ b/sdk/typescript/examples/.gitignore @@ -0,0 +1,2 @@ +yarn.lock +package-lock.json diff --git a/sdk/typescript/examples/.webpack/webpack.base.js b/sdk/typescript/examples/.webpack/webpack.base.js new file mode 100644 index 0000000000..7725e12bc0 --- /dev/null +++ b/sdk/typescript/examples/.webpack/webpack.base.js @@ -0,0 +1,98 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); +// const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const WebpackFavicons = require('webpack-favicons'); +const Dotenv = require('dotenv-webpack'); +const path = require('path'); +const os = require('os'); + +/** + * Creates the default Webpack config + * @param baseDir The base directory path, e.g. pass `__dirname` of the webpack config file using this method + */ +const webpackCommon = (baseDir, htmlPath) => ({ + module: { + rules: [ + { + test: /\.tsx?$/, + use: [ + { + loader: 'thread-loader', + options: { workers: Math.max(2, os.cpus().length - 1) }, + }, + { loader: 'ts-loader', options: { happyPackMode: true } }, + ], + exclude: /node_modules/, + }, + { + test: /\.css$/i, + use: ['style-loader', 'css-loader'], + }, + { + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + use: ['@svgr/webpack'], + }, + { + test: /\.(png|jpe?g|gif|md|webp)$/i, + // More information here https://webpack.js.org/guides/asset-modules/ + type: 'asset', + }, + { + // See https://webpack.js.org/guides/asset-management/#loading-fonts + test: /\.(woff|woff2|eot|ttf|otf)$/i, + type: 'asset/resource', + }, + { + test: /\.ya?ml$/, + type: 'json', + use: 'yaml-loader', + }, + ], + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + plugins: [new TsconfigPathsPlugin()], + alias: { + 'react/jsx-runtime': require.resolve('react/jsx-runtime'), + }, + }, + plugins: [ + // new CleanWebpackPlugin(), + + ...(Array.isArray(htmlPath) + ? htmlPath.map((item) => new HtmlWebpackPlugin(item)) + : [ + new HtmlWebpackPlugin({ + filename: 'index.html', + template: path.resolve(baseDir, htmlPath || 'src/index.html'), + }), + ]), + + new ForkTsCheckerWebpackPlugin({ + typescript: { + mode: 'write-references', + diagnosticOptions: { + semantic: true, + syntactic: true, + }, + }, + }), + + new WebpackFavicons({ + src: path.resolve(__dirname, '../../assets/favicon/favicon.png'), // the asset directory is relative to THIS file + }), + + new Dotenv(), + ], + output: { + path: path.resolve(baseDir, 'dist'), + publicPath: '/', + }, +}); + + +module.exports = { + webpackCommon, +}; diff --git a/sdk/typescript/examples/docs/packet_format.svg b/sdk/typescript/examples/docs/packet_format.svg index 9cb2bfb6eb..6e7f1fdc49 100644 --- a/sdk/typescript/examples/docs/packet_format.svg +++ b/sdk/typescript/examples/docs/packet_format.svg @@ -1,4 +1,45 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Do not edit this file with editors other than diagrams.net --> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="901px" height="131px" viewBox="-0.5 -0.5 901 131" content="<mxfile host="app.diagrams.net" modified="2022-11-08T11:04:05.546Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" version="20.5.3" etag="l8XsiT4SYakS9MzWn56p" type="google"><diagram id="Zc81FqG-c75uc3H8I5Jm">xZZNj5swEIZ/DVJ7WAkMS8hxN/vRQ/eUVj1WTjzBVo2NjLMk/fUdg4GwkHalTZVLsF/PeJjndZwE8ao4PBta8hfNQAYkZIcgfggIiQhZ4sMpx1ZZxKQVciOYDxqEtfgNXgy9uhcMqlGg1VpaUY7FrVYKtnakUWN0PQ7baTmuWtIcJsJ6S+VU/SGY5a2akcWgfwGR865ylPqGC9oF+04qTpmuT6T4MYhXRmvbjorDCqSD13Fp857OrPYvZkDZ9yR47q9U7n1vAUklpt5vcJC7wb1Q1BwxaMWpxcddWeLnC1SVo+SjsUKf4Duzxw6X0XvFwFUMcbnmwsK6pFu3WuMBQY3bQuIswuErGCsQ9Z0UuULNahdA/UzCzpWoMF2o/GszeyB9UZcMh7Msop4wHk3QBdimMZ+w8J74Q7n003pweBl6jZ+6G3uR+lOV9zsP4HHg2c/7EE98+CUU670wHdtPe6Fs9vljiC/AKhuzisgUVjLDKrkAqmSCigNlYH5W7p6YJ5Ym10fWn5O/MIvJf4J2ewZaNQPs+7enm6xhZfA71hYmYXPw8Lk5WmivUeqa0zsX2ZKnhaOmNlXZEGs3Ft2+My7hW/fL1zcoSf9t0O3cDXAJg9KJQYxaeu4CeOPD9dm9vQ8WU3RZdhl0OB1+H5u1k38Z8eMf</diagram></mxfile>" style="background-color: rgb(255, 255, 255);"><defs/><g><rect x="0" y="0" width="900" height="130" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 896px; height: 1px; padding-top: 7px; margin-left: 4px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><b>Binary Chat App Message</b></div></div></div></foreignObject><text x="4" y="19" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px">Binary Chat App Message</text></switch></g><rect x="10" y="30" width="40" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 50px; margin-left: 11px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">kind<br />(uint8)</div></div></div></foreignObject><text x="30" y="54" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">kind...</text></switch></g><rect x="60" y="30" width="320" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 318px; height: 1px; padding-top: 50px; margin-left: 61px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">header_size<br />(uint64)</div></div></div></foreignObject><text x="220" y="54" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">header_size...</text></switch></g><rect x="390" y="30" width="500" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 498px; height: 1px; padding-top: 50px; margin-left: 391px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">headers<br />(UTF-8 string as uint8 byte array of size <i>header_size</i>)</div></div></div></foreignObject><text x="640" y="54" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">headers...</text></switch></g><rect x="10" y="80" width="880" height="40" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 878px; height: 1px; padding-top: 100px; margin-left: 11px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">data<br />(uint8 byte array)</div></div></div></foreignObject><text x="450" y="104" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">data...</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg> \ No newline at end of file +<svg width="893px" height="121px" viewBox="0 0 893 121" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <title>packet_format + + + + + + + + + Nym WASM client payload + + + + + + + + header_size.. + . + + + + + + + + headers... + + + + + + + + data... + + + + + + + + \ No newline at end of file diff --git a/sdk/typescript/examples/docs/packet_jpeg.svg b/sdk/typescript/examples/docs/packet_jpeg.svg index 324994c49f..ba164a3c5e 100644 --- a/sdk/typescript/examples/docs/packet_jpeg.svg +++ b/sdk/typescript/examples/docs/packet_jpeg.svg @@ -1,4 +1,44 @@ - - -
Chat App Binary Message (JPEG image)
Chat App Binary Message (JPEG image)
1
1
00 00 00 00 00 00 00 32
00 00 00 00 00 00 00 32
'{"filename": "test.jpeg", mimeType: "image/jpeg"}'
...
'{"filename": "test.jpeg", mimeType: "image/jpeg"}'...
d8 ff ed ff 84 00 ...
d8 ff ed ff 84 00 ...
Text is not SVG - cannot display
\ No newline at end of file + + packet_format + + + + + + + + + JPEG payload + + + + + + + + 52 (as unsigned 64 bit integer big endian) + + + + + + + + { “mime-type”: “image/jpeg” } + + + + + + + + image bytes… + + + + + + + + \ No newline at end of file diff --git a/sdk/typescript/examples/docs/packet_string.svg b/sdk/typescript/examples/docs/packet_string.svg index 25f4421bf4..eb88a3898c 100644 --- a/sdk/typescript/examples/docs/packet_string.svg +++ b/sdk/typescript/examples/docs/packet_string.svg @@ -1,4 +1,44 @@ - - -
Chat App String Message
Chat App String Message
0
0
0
0
(not present)
(not present)
"Hello world"
65 48 6c 6c 20 6f 6f 77 6c 72 0a 64
"Hello world"...
Text is not SVG - cannot display
\ No newline at end of file + + packet_format + + + + + + + + + UTF-8 string payload + + + + + + + + 51 (as unsigned 64 bit integer big endian) + + + + + + + + { “mime-type”: “text/plain” } + + + + + + + + text data encoded as UTF-8 + + + + + + + + \ No newline at end of file diff --git a/sdk/typescript/examples/docs/sphinx.svg b/sdk/typescript/examples/docs/sphinx.svg index d01a0d3946..19889a761c 100644 --- a/sdk/typescript/examples/docs/sphinx.svg +++ b/sdk/typescript/examples/docs/sphinx.svg @@ -1,4 +1,101 @@ - - -
Encrypted wrapper with gateway <=> client long-term session key
Encrypted wrapper with gateway <=> client long-term session key
Sphinx Packet
Sphinx Packet
Mixnode 1 Layer
Mixnode 1 Layer
Mixnode 2 Layer
Mixnode 2 Layer
Mixnode 3 Layer
Mixnode 3 Layer
Egress Gateway Layer
Egress Gateway Layer
End-to-end encryption with ephemeral key (only known to chat app, so gateway can't view payload)
End-to-end encryption with ephemeral key (only known to chat app, so gateway can't view payload)
Binary Chat App Message
Binary Chat App Message
kind
(uint8)
kind...
header_size
(uint64)
header_size...
headers
(UTF-8 string as uint8 byte array of size header_size)
headers...
data
(uint8 byte array)
data...
Text is not SVG - cannot display
\ No newline at end of file + + sphinx + + + + + + + + + Encrypted wrapper with gateway <=> client long-term session key + + + + + + + + Sphinx Packet + + + + + + + + Mixnode 1 Layer + + + + + + + + Mixnode 2 Layer + + + + + + + + Mixnode 3 Layer + + + + + + + + Egress Gateway Layer + + + + + + + + End-to-end encryption with ephemeral key (only known to chat app, so gateway can't view payload) + + + + + + + + Nym WASM client payload + + + + + + + + header_size.. + . + + + + + + + + { “mime-type”: “application/octet-stream”, ... other headers ... } + + + + + + + + data... + + + + + + + + \ No newline at end of file diff --git a/sdk/typescript/examples/docs/sphinx2.svg b/sdk/typescript/examples/docs/sphinx2.svg deleted file mode 100644 index 0552bd9585..0000000000 --- a/sdk/typescript/examples/docs/sphinx2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
Encrypted wrapper with gateway <=> client long-term session key
Encrypted wrapper with gateway <=> client long-term session key
Sphinx Packet
Sphinx Packet
Mixnode 1 Layer
Mixnode 1 Layer
Mixnode 2 Layer
Mixnode 2 Layer
Mixnode 3 Layer
Mixnode 3 Layer
Egress Gateway Layer
Egress Gateway Layer
End-to-end encryption with ephemeral key (only known to chat app, so gateway can't view payload)
End-to-end encryption with ephemeral key (only known to chat app, so gateway can't view payload)
Binary Chat App Message
Binary Chat App Message
string message converted as UTF-8 to uint8 byte array
(uint8 byte array)
string message converted as UTF-8 to uint8 byte array...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/sdk/typescript/examples/parcel/README.md b/sdk/typescript/examples/parcel/README.md new file mode 100644 index 0000000000..5623b44c2f --- /dev/null +++ b/sdk/typescript/examples/parcel/README.md @@ -0,0 +1,15 @@ +# HTML Nym Mixnet Chat App bundled with Parcel + +This is an example of using the Nym Mixnet to send text chat messages in an app bundled with Parcel 2.0. + +You can use this example as a seed for a new project. + +## Running the example + +Try out the chat app by running: + +``` +npm install +npm start +``` + diff --git a/sdk/typescript/examples/parcel/package.json b/sdk/typescript/examples/parcel/package.json new file mode 100644 index 0000000000..ad5bc10384 --- /dev/null +++ b/sdk/typescript/examples/parcel/package.json @@ -0,0 +1,43 @@ +{ + "name": "@nymproject/sdk-example-plain-html-parcel", + "description": "An example project that uses WASM and plain HTML bundled with Parcel v2", + "version": "1.0.0", + "license": "Apache-2.0", + "source": "src/index.html", + "browserslist": "> 0.5%, last 2 versions, not dead", + "dependencies": { + "@nymproject/sdk": "1" + }, + "devDependencies": { + "@types/jest": "^27.0.1", + "@types/node": "^16.7.13", + "@typescript-eslint/eslint-plugin": "^5.13.0", + "@typescript-eslint/parser": "^5.13.0", + "eslint": "^8.10.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^16.1.0", + "eslint-config-prettier": "^8.5.0", + "eslint-import-resolver-root-import": "^1.0.4", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^26.1.1", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-prettier": "^4.0.0", + "jest": "^27.1.0", + "mini-css-extract-plugin": "^2.2.2", + "npm-run-all": "^4.1.5", + "prettier": "^2.5.1", + "ts-jest": "^27.0.5", + "typescript": "^4.6.2" + }, + "scripts": { + "start": "npx parcel", + "build": "npx parcel build", + "build:serve": "npx serve dist", + "test": "jest", + "test:watch": "jest --watch", + "tsc": "tsc", + "tsc:watch": "tsc --watch", + "lint": "eslint src", + "lint:fix": "eslint src --fix" + } +} diff --git a/sdk/typescript/examples/parcel/src/dom-utils.ts b/sdk/typescript/examples/parcel/src/dom-utils.ts new file mode 100644 index 0000000000..7e98532ea3 --- /dev/null +++ b/sdk/typescript/examples/parcel/src/dom-utils.ts @@ -0,0 +1,62 @@ +import { NymMixnetClient, MimeTypes } from '@nymproject/sdk'; + +/** + * Create a Sphinx packet and send it to the mixnet through the gateway node. + * + * Message and recipient are taken from the values in the user interface. + * + * @param {Client} nymClient the nym client to use for message sending + */ +export async function sendMessageTo(nym: NymMixnetClient) { + const message = (document.getElementById('message') as HTMLFormElement).value; + const recipient = (document.getElementById('recipient') as HTMLFormElement).value; + + await nym.client.send({ payload: { message, mimeType: MimeTypes.TextPlain }, recipient }); + displaySend(message); +} + +/** + * Display messages that have been sent up the websocket. Colours them blue. + * + * @param {string} message + */ +function displaySend(message: string) { + const timestamp = new Date().toISOString().substr(11, 12); + + const sendDiv = document.createElement('div'); + const paragraph = document.createElement('p'); + paragraph.setAttribute('style', 'color: blue'); + const paragraphContent = document.createTextNode(`${timestamp} sent >>> ${message}`); + paragraph.appendChild(paragraphContent); + + sendDiv.appendChild(paragraph); + document.getElementById('output')?.appendChild(sendDiv); +} + +/** + * Display received text messages in the browser. Colour them green. + * + * @param {string} message + */ +export function displayReceived(message: string) { + const content = message; + + const timestamp = new Date().toLocaleTimeString(); + const receivedDiv = document.createElement('div'); + const paragraph = document.createElement('p'); + paragraph.setAttribute('style', 'color: green'); + const paragraphContent = document.createTextNode(`${timestamp} received >>> ${content}`); + // const paragraphContent = document.createTextNode(timestamp + " received >>> " + content + ((replySurb != null) ? "Reply SURB was attached here (but we can't do anything with it yet" : " (NO REPLY-SURB AVAILABLE)")) + paragraph.appendChild(paragraphContent); + receivedDiv.appendChild(paragraph); + document.getElementById('output')?.appendChild(receivedDiv); +} + +/** + * Display the nymClient's sender address in the user interface + * + * @param {Client} nymClient + */ +export function displaySenderAddress(address: string) { + (document.getElementById('sender') as HTMLFormElement).value = address; +} diff --git a/sdk/typescript/examples/parcel/src/index.html b/sdk/typescript/examples/parcel/src/index.html new file mode 100644 index 0000000000..4e4550c539 --- /dev/null +++ b/sdk/typescript/examples/parcel/src/index.html @@ -0,0 +1,36 @@ + + + + + + + Nym WebAssembly Demo + + + + +

+ +

+ +

+ +

+

+ +

+

+ +

+ +

Send messages from your browser, through the mixnet, and to the recipient using the "send" button.

+

Sent messages show in blue, received + messages show in green.

+ +
+

+

+

+ + + diff --git a/sdk/typescript/examples/parcel/src/index.ts b/sdk/typescript/examples/parcel/src/index.ts new file mode 100644 index 0000000000..3871d1c0a7 --- /dev/null +++ b/sdk/typescript/examples/parcel/src/index.ts @@ -0,0 +1,53 @@ +import { createNymMixnetClient, NymMixnetClient } from '@nymproject/sdk'; +import { displayReceived, sendMessageTo, displaySenderAddress } from './dom-utils'; + +let nym: NymMixnetClient | null = null; + +/** + * The main entry point + */ +async function main() { + nym = await createNymMixnetClient(); + + if (!nym) { + console.error('Oh no! Could not create client'); + return; + } + + const nymApiUrl = 'https://validator.nymtech.net/api'; + const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'; + + // subscribe to connect event, so that we can show the client's address + nym.events.subscribeToConnected((e) => { + if (e.args.address) { + displaySenderAddress(e.args.address); + } + }); + + // subscribe to message received events and show any string messages received + nym.events.subscribeToTextMessageReceivedEvent((e) => { + displayReceived(e.args.payload); + }); + + const sendButton: HTMLButtonElement = document.querySelector('#send-button') as HTMLButtonElement; + if (sendButton) { + sendButton.onclick = function () { + if (nym) { + sendMessageTo(nym); + } + }; + } + + // start up the client + await nym.client.start({ + clientId: 'My awesome client', + nymApiUrl, + preferredGatewayIdentityKey, + }); +} + +// wait for the html to load +window.addEventListener('DOMContentLoaded', () => { + // let's do this! + main(); +}); diff --git a/sdk/typescript/examples/parcel/tsconfig.json b/sdk/typescript/examples/parcel/tsconfig.json new file mode 100644 index 0000000000..b8059e8698 --- /dev/null +++ b/sdk/typescript/examples/parcel/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "outDir": "./dist" + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx" + ], + "exclude": [ + "node_modules", + "build", + "dist" + ] +} diff --git a/sdk/typescript/examples/plain-html/README.md b/sdk/typescript/examples/plain-html/README.md index cd071de5ef..4cc2c16e8e 100644 --- a/sdk/typescript/examples/plain-html/README.md +++ b/sdk/typescript/examples/plain-html/README.md @@ -4,11 +4,13 @@ This is an example of using the Nym Mixnet to send text chat messages, with opti You can use this example as a seed for a new project. +## Running the example + Try out the chat app by running: ``` -yarn -yarn start +npm install +npm start ``` ## How does it work? @@ -20,7 +22,7 @@ a WASM library that builds and encrypts Sphinx packets in the browser to send ov The WASM code encrypts each layer of the Sphinx packet in the browser, before sending the Sphinx packet over a websocket to the ingress gateway: -![Sphinx packet](../docs/sphinx2.svg) +![Sphinx packet](../docs/sphinx.svg) diff --git a/sdk/typescript/examples/plain-html/package.json b/sdk/typescript/examples/plain-html/package.json index bd4415c528..a11f9b7639 100644 --- a/sdk/typescript/examples/plain-html/package.json +++ b/sdk/typescript/examples/plain-html/package.json @@ -11,16 +11,14 @@ "@babel/plugin-transform-async-to-generator": "^7.14.5", "@babel/preset-env": "^7.15.0", "@babel/preset-typescript": "^7.15.0", - "@nymproject/eslint-config-react-typescript": "^1.0.0", - "@nymproject/webpack": "^1.0.0", "@types/jest": "^27.0.1", "@types/node": "^16.7.13", "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.13.0", - "babel-loader": "^8.2.2", + "babel-loader": "^8.3.0", "babel-plugin-root-import": "^5.1.0", "clean-webpack-plugin": "^4.0.0", - "css-loader": "^6.2.0", + "css-loader": "^6.7.3", "css-minimizer-webpack-plugin": "^3.0.2", "dotenv-webpack": "^7.0.3", "eslint": "^8.10.0", @@ -39,26 +37,20 @@ "mini-css-extract-plugin": "^2.2.2", "npm-run-all": "^4.1.5", "prettier": "^2.5.1", - "style-loader": "^3.2.1", + "style-loader": "^3.3.1", "thread-loader": "^3.0.4", "ts-jest": "^27.0.5", - "ts-loader": "^9.2.5", + "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^3.5.2", "typescript": "^4.6.2", "url-loader": "^4.1.1", - "webpack": "^5.64.3", + "webpack": "^5.75.0", "webpack-cli": "^4.8.0", "webpack-dev-server": "^4.5.0", "webpack-merge": "^5.8.0" }, "scripts": { - "prestart": "yarn build:dependencies", "start": "webpack serve --progress --port 3000", - "build:dependencies": "run-s build:dependencies:nym-client-wasm build:dependencies:ts-packages build:dependencies:sdk", - "build:dependencies:nym-client-wasm": "../../packages/nym-client-wasm/scripts/build.sh", - "build:dependencies:ts-packages": "cd ../../../.. && yarn && yarn build", - "build:dependencies:sdk": "cd ../../packages/sdk && yarn build", - "prebuild": "yarn build:dependencies", "build": "webpack build --progress --config webpack.prod.js", "build:dev": "webpack build --progress", "build:serve": "npx serve dist", diff --git a/sdk/typescript/examples/plain-html/src/dom-utils.ts b/sdk/typescript/examples/plain-html/src/dom-utils.ts index b2926cbcec..7e98532ea3 100644 --- a/sdk/typescript/examples/plain-html/src/dom-utils.ts +++ b/sdk/typescript/examples/plain-html/src/dom-utils.ts @@ -1,4 +1,4 @@ -import { NymMixnetClient } from '@nymproject/sdk'; +import { NymMixnetClient, MimeTypes } from '@nymproject/sdk'; /** * Create a Sphinx packet and send it to the mixnet through the gateway node. @@ -8,11 +8,11 @@ import { NymMixnetClient } from '@nymproject/sdk'; * @param {Client} nymClient the nym client to use for message sending */ export async function sendMessageTo(nym: NymMixnetClient) { - const payload = (document.getElementById('message') as HTMLFormElement).value; + const message = (document.getElementById('message') as HTMLFormElement).value; const recipient = (document.getElementById('recipient') as HTMLFormElement).value; - await nym.client.sendMessage({ payload, recipient }); - displaySend(payload); + await nym.client.send({ payload: { message, mimeType: MimeTypes.TextPlain }, recipient }); + displaySend(message); } /** diff --git a/sdk/typescript/examples/plain-html/src/index.ts b/sdk/typescript/examples/plain-html/src/index.ts index 16f53c380c..a0e429beb1 100644 --- a/sdk/typescript/examples/plain-html/src/index.ts +++ b/sdk/typescript/examples/plain-html/src/index.ts @@ -9,18 +9,16 @@ let nym: NymMixnetClient | null = null; async function main() { nym = await createNymMixnetClient(); + // add nym client to the Window globally, so that it can be used from the dev tools console + (window as any).nym = nym; + if (!nym) { console.error('Oh no! Could not create client'); return; } - // // mixnet v1 - // const validatorApiUrl = 'https://validator.nymtech.net/api'; - // const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'; - - // mixnet v2 - const validatorApiUrl = 'https://qwerty-validator-api.qa.nymte.ch/api'; // "http://localhost:8081"; - const preferredGatewayIdentityKey = undefined; // '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL'; + const nymApiUrl = 'https://validator.nymtech.net/api'; + const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'; // subscribe to connect event, so that we can show the client's address nym.events.subscribeToConnected((e) => { @@ -43,10 +41,12 @@ async function main() { }; } + nym.events.subscribeToRawMessageReceivedEvent((e) => console.log('Received: ', e.args.payload)); + // start up the client await nym.client.start({ clientId: 'My awesome client', - validatorApiUrl, + nymApiUrl, preferredGatewayIdentityKey, }); } diff --git a/sdk/typescript/examples/plain-html/webpack.common.js b/sdk/typescript/examples/plain-html/webpack.common.js index 40e98f6296..3d5f37c912 100644 --- a/sdk/typescript/examples/plain-html/webpack.common.js +++ b/sdk/typescript/examples/plain-html/webpack.common.js @@ -1,6 +1,6 @@ const path = require('path'); const { mergeWithRules } = require('webpack-merge'); -const { webpackCommon } = require('@nymproject/webpack'); +const { webpackCommon } = require('../.webpack/webpack.base'); module.exports = mergeWithRules({ module: { @@ -21,7 +21,6 @@ module.exports = mergeWithRules({ { entry: { index: path.resolve(__dirname, 'src/index.ts'), - worker: require.resolve('@nymproject/sdk/mixnet/wasm/worker.js'), }, output: { path: path.resolve(__dirname, 'dist'), diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/README.md b/sdk/typescript/examples/react-webpack-with-theme-example/README.md index 4afa796f9b..9a773a9e0f 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/README.md +++ b/sdk/typescript/examples/react-webpack-with-theme-example/README.md @@ -10,11 +10,13 @@ You can use this example as a seed for a new project, and it uses: - Web Workers - Material UI (MUI) +## Running the example + Try out the chat app by running: ``` -yarn -yarn start +npm install +npm start ``` ## How does it work? diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/package.json b/sdk/typescript/examples/react-webpack-with-theme-example/package.json index 99dd547454..ec780698d8 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/package.json +++ b/sdk/typescript/examples/react-webpack-with-theme-example/package.json @@ -8,11 +8,10 @@ "@mui/lab": "^5.0.0-alpha.72", "@mui/material": "^5.0.1", "@mui/styles": "^5.0.1", - "@nymproject/mui-theme": "^1.0.0", - "@nymproject/react": "^1.0.0", + "react-mui-dropzone": "^4.0.6", "@nymproject/sdk": "1", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-mui-dropzone": "^4.0.6", "use-clipboard-copy": "^0.2.0" @@ -23,21 +22,20 @@ "@babel/preset-env": "^7.15.0", "@babel/preset-react": "^7.14.5", "@babel/preset-typescript": "^7.15.0", - "@nymproject/eslint-config-react-typescript": "^1.0.0", - "@nymproject/webpack": "^1.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", "@svgr/webpack": "^6.1.1", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.0.0", "@types/jest": "^27.0.1", "@types/node": "^16.7.13", - "@types/react": "^17.0.34", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.10", "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.13.0", - "babel-loader": "^8.2.2", + "babel-loader": "^8.3.0", "babel-plugin-root-import": "^5.1.0", "clean-webpack-plugin": "^4.0.0", - "css-loader": "^6.2.0", + "css-loader": "^6.7.3", "css-minimizer-webpack-plugin": "^3.0.2", "dotenv-webpack": "^7.0.3", "eslint": "^8.10.0", @@ -61,28 +59,27 @@ "npm-run-all": "^4.1.5", "prettier": "^2.5.1", "react-refresh-typescript": "^2.0.3", - "style-loader": "^3.2.1", + "style-loader": "^3.3.1", "thread-loader": "^3.0.4", "ts-jest": "^27.0.5", - "ts-loader": "^9.2.5", + "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^3.5.2", "typescript": "^4.6.2", "url-loader": "^4.1.1", - "webpack": "^5.64.3", + "webpack": "^5.75.0", "webpack-cli": "^4.8.0", "webpack-dev-server": "^4.5.0", "webpack-favicons": "^1.3.8", "webpack-merge": "^5.8.0" }, + "overrides": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.10" + }, "scripts": { - "prestart": "yarn build:dependencies", "start": "webpack serve --progress --port 3000", - "start:only-this": "webpack serve --progress --port 3000", - "build:dependencies": "run-s build:dependencies:nym-client-wasm build:dependencies:ts-packages build:dependencies:sdk", - "build:dependencies:nym-client-wasm": "../../packages/nym-client-wasm/scripts/build.sh", - "build:dependencies:ts-packages": "cd ../../../.. && yarn && yarn build", - "build:dependencies:sdk": "cd ../../packages/sdk && yarn build", - "prebuild": "yarn build:dependencies", "build": "webpack build --progress --config webpack.prod.js", "build:dev": "webpack build --progress", "build:serve": "npx serve dist", diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx b/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx index 6f6f4c2039..c2252585de 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx +++ b/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx @@ -20,19 +20,20 @@ import CallReceivedIcon from '@mui/icons-material/CallReceived'; import PersonIcon from '@mui/icons-material/Person'; import PersonOffIcon from '@mui/icons-material/PersonOff'; import ErrorIcon from '@mui/icons-material/Error'; -import { NymLogo } from '@nymproject/react/logo/NymLogo'; -import { NymThemeProvider } from '@nymproject/mui-theme'; import { useTheme } from '@mui/material/styles'; import { useClipboard } from 'use-clipboard-copy'; import { DropzoneDialog } from 'react-mui-dropzone'; import UploadFileIcon from '@mui/icons-material/UploadFile'; import ArticleIcon from '@mui/icons-material/Article'; import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'; +import { NymThemeProvider } from './theme'; import { ThemeToggle } from './ThemeToggle'; import { AppContextProvider, useAppContext } from './context'; import { MixnetContextProvider, parseBinaryMessageHeaders, useMixnetContext } from './context/mixnet'; +// eslint-disable-next-line import/no-relative-packages +import Logo from '../../../../../assets/logo/logo-circle.svg'; -export const AppTheme: React.FC = ({ children }) => { +export const AppTheme: FCWithChildren = ({ children }) => { const { mode } = useAppContext(); return {children}; @@ -52,7 +53,7 @@ interface UploadState { files: File[]; } -export const Content: React.FC = () => { +export const Content: FCWithChildren = () => { const theme = useTheme(); const { isReady, address, connect, events, sendTextMessage, sendBinaryMessage } = useMixnetContext(); const copy = useClipboard(); @@ -69,7 +70,8 @@ export const Content: React.FC = () => { }; const log = React.useRef([]); - const [_logTrigger, setLogTrigger] = React.useState(Date.now()); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [logTrigger, setLogTrigger] = React.useState(Date.now()); const [uploadState, setUploadState] = React.useState({ dialogOpen: false, @@ -94,17 +96,12 @@ export const Content: React.FC = () => { React.useEffect(() => { if (isReady) { - // // mixnet v1 - // const validatorApiUrl = 'https://validator.nymtech.net/api'; - // const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'; - - // mixnet v2 - const validatorApiUrl = 'https://qwerty-validator-api.qa.nymte.ch/api'; // "http://localhost:8081"; - const preferredGatewayIdentityKey = undefined; // '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL'; + const nymApiUrl = 'https://validator.nymtech.net/api'; + const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'; connect({ clientId: 'Example Client', - validatorApiUrl, + nymApiUrl, preferredGatewayIdentityKey, }); } @@ -143,13 +140,13 @@ export const Content: React.FC = () => { if (events) { const unsubcribe = events.subscribeToBinaryMessageReceivedEvent((e) => { // the headers will be JSON (see the mixnet context for how they are created), so parse them - const headers = parseBinaryMessageHeaders(e.args.headers); + const headers = e.args.headers ? parseBinaryMessageHeaders(e.args.headers) : undefined; - const blob = new Blob([new Uint8Array(e.args.payload)], { type: headers.mimeType }); + const blob = new Blob([new Uint8Array(e.args.payload)], { type: headers?.mimeType }); log.current.push({ kind: 'rx', timestamp: new Date(), - filename: headers.filename, + filename: headers?.filename, fileDownloadUrl: URL.createObjectURL(blob), filesize: e.args.payload.length, }); @@ -231,7 +228,7 @@ export const Content: React.FC = () => { - +

Nym Mixnet Chat App

@@ -405,7 +402,7 @@ export const Content: React.FC = () => { ); }; -export const App: React.FC = () => ( +export const App: FCWithChildren = () => ( diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/ThemeToggle.tsx b/sdk/typescript/examples/react-webpack-with-theme-example/src/ThemeToggle.tsx index e52ed5dce5..2739f429e9 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/src/ThemeToggle.tsx +++ b/sdk/typescript/examples/react-webpack-with-theme-example/src/ThemeToggle.tsx @@ -4,7 +4,7 @@ import DarkModeIcon from '@mui/icons-material/DarkMode'; import LightModeIcon from '@mui/icons-material/LightMode'; import { useAppContext } from './context'; -export const ThemeToggle: React.FC = () => { +export const ThemeToggle: FCWithChildren = () => { const { mode, toggleMode } = useAppContext(); return ( diff --git a/ts-packages/react-components/src/playground/checkboxes.tsx b/ts-packages/react-components/src/playground/checkboxes.tsx index 90a020e2e0..a903fc1f59 100644 --- a/ts-packages/react-components/src/playground/checkboxes.tsx +++ b/ts-packages/react-components/src/playground/checkboxes.tsx @@ -3,7 +3,7 @@ import Checkbox from '@mui/material/Checkbox'; const label = { inputProps: { 'aria-label': 'Checkbox demo' } }; -export const PlaygroundCheckboxes: React.FC = () => ( +export const PlaygroundCheckboxes: FCWithChildren = () => (
diff --git a/ts-packages/react-components/src/playground/fonts.tsx b/ts-packages/react-components/src/playground/fonts.tsx index 5bbd822905..a7347c0191 100644 --- a/ts-packages/react-components/src/playground/fonts.tsx +++ b/ts-packages/react-components/src/playground/fonts.tsx @@ -4,7 +4,7 @@ const CONTENT = 'The quick brown fox jumped over the white fence'; const WEIGHTS = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]; -export const PlaygroundFonts: React.FC = () => ( +export const PlaygroundFonts: FCWithChildren = () => (
{WEIGHTS.map((fontWeight) => (
diff --git a/ts-packages/react-components/src/playground/switches.tsx b/ts-packages/react-components/src/playground/switches.tsx index edda1654a9..dde14e1e2b 100644 --- a/ts-packages/react-components/src/playground/switches.tsx +++ b/ts-packages/react-components/src/playground/switches.tsx @@ -3,7 +3,7 @@ import Switch from '@mui/material/Switch'; const label = { inputProps: { 'aria-label': 'Switch demo' } }; -export const PlaygroundBasicSwitches: React.FC = () => ( +export const PlaygroundBasicSwitches: FCWithChildren = () => (
diff --git a/ts-packages/react-components/src/playground/theme.tsx b/ts-packages/react-components/src/playground/theme.tsx index 609dd86aa9..8bab62e51b 100644 --- a/ts-packages/react-components/src/playground/theme.tsx +++ b/ts-packages/react-components/src/playground/theme.tsx @@ -3,7 +3,7 @@ import { useTheme } from '@mui/material'; import { MUIThemeExplorer } from './theme/MUIThemeExplorer'; import { PaletteSwatches, PaletteSwatchesList } from './theme/PaletteSwatches'; -export const PlaygroundTheme: React.FC = () => { +export const PlaygroundTheme: FCWithChildren = () => { const theme = useTheme(); return ( <> @@ -15,7 +15,7 @@ export const PlaygroundTheme: React.FC = () => { ); }; -export const PlaygroundPalette: React.FC = () => { +export const PlaygroundPalette: FCWithChildren = () => { const theme = useTheme(); return ; }; diff --git a/ts-packages/react-components/src/playground/theme/MUIThemeExplorer.tsx b/ts-packages/react-components/src/playground/theme/MUIThemeExplorer.tsx index e395969a72..82b64dae9c 100644 --- a/ts-packages/react-components/src/playground/theme/MUIThemeExplorer.tsx +++ b/ts-packages/react-components/src/playground/theme/MUIThemeExplorer.tsx @@ -7,7 +7,7 @@ import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import ArrowRightIcon from '@mui/icons-material/ArrowRight'; import { Box } from '@mui/material'; -const MUIThemeExplorerItem: React.FC<{ +const MUIThemeExplorerItem: FCWithChildren<{ path: string; parentKey: string; theme: Theme; @@ -82,7 +82,7 @@ const MUIThemeExplorerItem: React.FC<{ return null; }; -export const MUIThemeExplorer: React.FC<{ +export const MUIThemeExplorer: FCWithChildren<{ theme: Theme; }> = ({ theme }) => ( ); -export const PaletteSwatches: React.FC<{ +export const PaletteSwatches: FCWithChildren<{ theme: Theme; }> = ({ theme }) => { const swatches = React.useMemo(() => flatten(theme.palette), [theme.palette]); @@ -46,7 +46,7 @@ export const PaletteSwatches: React.FC<{ ); }; -export const PaletteSwatchesList: React.FC<{ +export const PaletteSwatchesList: FCWithChildren<{ theme: Theme; }> = ({ theme }) => { const swatches = React.useMemo(() => flatten(theme.palette), [theme.palette]); diff --git a/ts-packages/react-components/src/typings/FC.d.ts b/ts-packages/react-components/src/typings/FC.d.ts new file mode 100644 index 0000000000..08ebdfa298 --- /dev/null +++ b/ts-packages/react-components/src/typings/FC.d.ts @@ -0,0 +1 @@ +declare type FCWithChildren

= React.FC>; diff --git a/ts-packages/react-webpack-with-theme-example/README.md b/ts-packages/react-webpack-with-theme-example/README.md index 37a3029f11..a95d916957 100644 --- a/ts-packages/react-webpack-with-theme-example/README.md +++ b/ts-packages/react-webpack-with-theme-example/README.md @@ -51,7 +51,7 @@ Storybook is available in [@nymproject/react](../react-components/src/stories/In The [Nym theme](../mui-theme/src/theme/theme.ts) provides a theme provider that you can add as follows: ```typescript jsx -export const App: React.FC = () => ( +export const App: FCWithChildren = () => ( @@ -59,13 +59,13 @@ export const App: React.FC = () => ( ); -export const AppTheme: React.FC = ({ children }) => { +export const AppTheme: FCWithChildren = ({ children }) => { const { mode } = useAppContext(); return {children}; }; -export const Content: React.FC = () => { +export const Content: FCWithChildren = () => { ... } ``` diff --git a/ts-packages/react-webpack-with-theme-example/package.json b/ts-packages/react-webpack-with-theme-example/package.json index e4d7c16b46..078ebc27cb 100644 --- a/ts-packages/react-webpack-with-theme-example/package.json +++ b/ts-packages/react-webpack-with-theme-example/package.json @@ -4,14 +4,14 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", "@mui/material": "^5.0.1", "@mui/styles": "^5.0.1", "@mui/icons-material": "^5.5.0", "@mui/lab": "^5.0.0-alpha.72", - "@nymproject/mui-theme" : "^1.0.0", - "@nymproject/react" : "^1.0.0" + "@nymproject/mui-theme": "^1.0.0", + "@nymproject/react": "^1.0.0" }, "devDependencies": { "@babel/core": "^7.15.0", @@ -27,13 +27,13 @@ "@testing-library/react": "^12.0.0", "@types/jest": "^27.0.1", "@types/node": "^16.7.13", - "@types/react": "^17.0.34", + "@types/react": "^18.0.26", "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.13.0", - "babel-loader": "^8.2.2", + "babel-loader": "^8.3.0", "babel-plugin-root-import": "^5.1.0", "clean-webpack-plugin": "^4.0.0", - "css-loader": "^6.2.0", + "css-loader": "^6.7.3", "css-minimizer-webpack-plugin": "^3.0.2", "dotenv-webpack": "^7.0.3", "eslint": "^8.10.0", @@ -56,14 +56,14 @@ "mini-css-extract-plugin": "^2.2.2", "prettier": "^2.5.1", "react-refresh-typescript": "^2.0.3", - "style-loader": "^3.2.1", + "style-loader": "^3.3.1", "thread-loader": "^3.0.4", "ts-jest": "^27.0.5", - "ts-loader": "^9.2.5", + "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^3.5.2", "typescript": "^4.6.2", "url-loader": "^4.1.1", - "webpack": "^5.64.3", + "webpack": "^5.75.0", "webpack-cli": "^4.8.0", "webpack-dev-server": "^4.5.0", "webpack-favicons": "^1.3.8", diff --git a/ts-packages/react-webpack-with-theme-example/src/App.tsx b/ts-packages/react-webpack-with-theme-example/src/App.tsx index 80ca1cc33a..6f70787807 100644 --- a/ts-packages/react-webpack-with-theme-example/src/App.tsx +++ b/ts-packages/react-webpack-with-theme-example/src/App.tsx @@ -8,13 +8,13 @@ import { useTheme } from '@mui/material/styles'; import { ThemeToggle } from './ThemeToggle'; import { AppContextProvider, useAppContext } from './context'; -export const AppTheme: React.FC = ({ children }) => { +export const AppTheme: FCWithChildren = ({ children }) => { const { mode } = useAppContext(); return {children}; }; -export const Content: React.FC = () => { +export const Content: FCWithChildren = () => { const { mode } = useAppContext(); const theme = useTheme(); const isMounted = useIsMounted(); @@ -70,7 +70,7 @@ export const Content: React.FC = () => { ); }; -export const App: React.FC = () => ( +export const App: FCWithChildren = () => ( diff --git a/ts-packages/react-webpack-with-theme-example/src/ThemeToggle.tsx b/ts-packages/react-webpack-with-theme-example/src/ThemeToggle.tsx index e52ed5dce5..2739f429e9 100644 --- a/ts-packages/react-webpack-with-theme-example/src/ThemeToggle.tsx +++ b/ts-packages/react-webpack-with-theme-example/src/ThemeToggle.tsx @@ -4,7 +4,7 @@ import DarkModeIcon from '@mui/icons-material/DarkMode'; import LightModeIcon from '@mui/icons-material/LightMode'; import { useAppContext } from './context'; -export const ThemeToggle: React.FC = () => { +export const ThemeToggle: FCWithChildren = () => { const { mode, toggleMode } = useAppContext(); return (