diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 9e42b31964..1a55fd34fa 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -38,7 +38,7 @@ jobs: - name: install npm uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Matrix - Node Install run: npm install working-directory: .github/workflows/support-files diff --git a/.github/workflows/build-ts-packages.yml b/.github/workflows/build-ts-packages.yml index ba744b1a65..7c0ead8933 100644 --- a/.github/workflows/build-ts-packages.yml +++ b/.github/workflows/build-ts-packages.yml @@ -16,7 +16,7 @@ jobs: - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Setup yarn run: npm install -g yarn - name: Build diff --git a/.github/workflows/cd-dev.yml b/.github/workflows/cd-dev.yml index ab5dd75f21..0ca463345f 100644 --- a/.github/workflows/cd-dev.yml +++ b/.github/workflows/cd-dev.yml @@ -16,7 +16,7 @@ jobs: - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: 18 - name: Install Rust stable uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index 6e7882785a..782a2c196d 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -16,7 +16,7 @@ jobs: - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: 18 - name: Install Rust stable uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml index 39432c90e3..82068fce76 100644 --- a/.github/workflows/ci-dev.yml +++ b/.github/workflows/ci-dev.yml @@ -16,7 +16,7 @@ jobs: - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: 18 - name: Install Rust stable uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index e18370669d..2523013204 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -16,7 +16,7 @@ jobs: - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: 18 - name: Install Rust stable uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/connect-desktop-ci.yml b/.github/workflows/connect-desktop-ci.yml index 5846212222..76f39572dc 100644 --- a/.github/workflows/connect-desktop-ci.yml +++ b/.github/workflows/connect-desktop-ci.yml @@ -20,11 +20,17 @@ jobs: - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Install Yarn run: npm install -g yarn - - run: yarn + + - name: Install project dependencies + run: cd ../.. && yarn --network-timeout 100000 + + - name: Install app dependencies + run: yarn continue-on-error: true + - name: Set environment from the example run: cp .env.sample .env - run: yarn storybook:build diff --git a/.github/workflows/network-explorer-lint.yml b/.github/workflows/network-explorer-lint.yml deleted file mode 100644 index 35c07e654d..0000000000 --- a/.github/workflows/network-explorer-lint.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Linting for Network Explorer (eslint/prettier) - -on: - pull_request: - paths: - - 'explorer/**' - -defaults: - run: - working-directory: explorer - -jobs: - build: - runs-on: custom-runner-linux - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 - with: - node-version: 16 - - name: Setup yarn - run: npm install -g yarn - - name: Run ESLint - # GitHub should automatically annotate the PR - run: yarn && yarn lint \ No newline at end of file diff --git a/.github/workflows/network-explorer.yml b/.github/workflows/network-explorer.yml index 9f50548105..b4431b0ed4 100644 --- a/.github/workflows/network-explorer.yml +++ b/.github/workflows/network-explorer.yml @@ -21,7 +21,7 @@ jobs: - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Setup yarn run: npm install -g yarn continue-on-error: true diff --git a/.github/workflows/nightly_build.yml b/.github/workflows/nightly_build.yml index 64b95f6368..1ee391d6a7 100644 --- a/.github/workflows/nightly_build.yml +++ b/.github/workflows/nightly_build.yml @@ -152,7 +152,7 @@ jobs: uses: actions/setup-node@v3 if: env.WORKFLOW_CONCLUSION == 'failure' with: - node-version: 16 + node-version: 18 - name: Matrix - Node Install if: env.WORKFLOW_CONCLUSION == 'failure' run: npm install diff --git a/.github/workflows/nightly_build_release.yml b/.github/workflows/nightly_build_release.yml index 71b7df2bd7..fa1c465c97 100644 --- a/.github/workflows/nightly_build_release.yml +++ b/.github/workflows/nightly_build_release.yml @@ -167,7 +167,7 @@ jobs: uses: actions/setup-node@v3 if: env.WORKFLOW_CONCLUSION == 'failure' with: - node-version: 16 + node-version: 18 - name: Matrix - Node Install if: env.WORKFLOW_CONCLUSION == 'failure' run: npm install diff --git a/.github/workflows/nightly_build_release2.yml b/.github/workflows/nightly_build_release2.yml index ee85a1c978..3c3f962582 100644 --- a/.github/workflows/nightly_build_release2.yml +++ b/.github/workflows/nightly_build_release2.yml @@ -167,7 +167,7 @@ jobs: uses: actions/setup-node@v3 if: env.WORKFLOW_CONCLUSION == 'failure' with: - node-version: 16 + node-version: 18 - name: Matrix - Node Install if: env.WORKFLOW_CONCLUSION == 'failure' run: npm install diff --git a/.github/workflows/nym-connect-publish-macos.yml b/.github/workflows/nym-connect-publish-macos.yml index 0c9b33e416..21ec00ce1e 100644 --- a/.github/workflows/nym-connect-publish-macos.yml +++ b/.github/workflows/nym-connect-publish-macos.yml @@ -15,15 +15,22 @@ jobs: fail-fast: false matrix: platform: [macos-latest] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - uses: actions/checkout@v2 - - name: Node v16 + - name: Node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Install Rust stable uses: actions-rs/toolchain@v1 with: @@ -51,11 +58,15 @@ jobs: security list-keychain -d user -s $KEYCHAIN_PATH - name: Create env file - uses: timheuer/base64-to-file@v1.1 + uses: timheuer/base64-to-file@v1.2 with: fileName: '.env' encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }} + - name: Install project dependencies + shell: bash + run: cd .. && yarn --network-timeout 100000 + - name: Install app dependencies and build it env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -73,7 +84,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: nym-connect_1.0.0_x64.dmg - path: nym-connect/desktop/target/release/bundle/dmg/nym-connect_1.0.0_x64.dmg + path: nym-connect/desktop/target/release/bundle/dmg/nym-connect_1*_x64.dmg retention-days: 30 - name: Clean up keychain @@ -81,10 +92,38 @@ jobs: run: | security delete-keychain $RUNNER_TEMP/app-signing.keychain-db - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: files: | nym-connect/desktop/target/release/bundle/dmg/*.dmg nym-connect/desktop/target/release/bundle/macos/*.app.tar.gz* + - id: release-info + name: Prepare release info + run: | + semver="${${{ github.ref_name }}##nym-connect-}" && semver="${semver##v}" + echo "version=$semver" >> "$GITHUB_OUTPUT" + echo "filename=nym-connect_$version_x64.dmg" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/dmg/nym-connect_*_x64.dmg') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect.app.tar.gz + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect.app.tar.gz.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: NymConnect + category: connect + platform: MacOS + secrets: inherit diff --git a/.github/workflows/nym-connect-publish-ubuntu.yml b/.github/workflows/nym-connect-publish-ubuntu.yml index 473371c9e0..f71c277e0c 100644 --- a/.github/workflows/nym-connect-publish-ubuntu.yml +++ b/.github/workflows/nym-connect-publish-ubuntu.yml @@ -15,8 +15,15 @@ jobs: fail-fast: false matrix: platform: [custom-runner-linux] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - uses: actions/checkout@v2 @@ -26,19 +33,25 @@ jobs: sudo apt-get install -y webkit2gtk-4.0 libayatana-appindicator3-dev continue-on-error: true - - name: Node v16 + - name: Node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Install Rust stable uses: actions-rs/toolchain@v1 with: toolchain: stable + + - name: Install project dependencies + shell: bash + run: cd .. && yarn --network-timeout 100000 + - name: Install app dependencies run: yarn + - name: Create env file - uses: timheuer/base64-to-file@v1.1 + uses: timheuer/base64-to-file@v1.2 with: fileName: '.env' encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }} @@ -53,13 +66,41 @@ jobs: uses: actions/upload-artifact@v3 with: name: nym-connect.AppImage.tar.gz - path: nym-connect/desktop/target/release/bundle/appimage/nym-connect_1.0.0_amd64.AppImage + path: nym-connect/desktop/target/release/bundle/appimage/nym-connect_1*_amd64.AppImage retention-days: 30 - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: files: | nym-connect/desktop/target/release/bundle/appimage/*.AppImage nym-connect/desktop/target/release/bundle/appimage/*.AppImage.tar.gz* + - id: release-info + name: Prepare release info + run: | + semver="${${{ github.ref_name }}##nym-connect-}" && semver="${semver##v}" + echo "version=$semver" >> "$GITHUB_OUTPUT" + echo "filename=nym-connect_$version_amd64.AppImage" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/appimage/nym-connect_*_amd64.AppImage') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: NymConnect + category: connect + platform: Ubuntu + secrets: inherit diff --git a/.github/workflows/nym-connect-publish-windows10.yml b/.github/workflows/nym-connect-publish-windows10.yml index 7bd0ac1bc6..8c793d88d0 100644 --- a/.github/workflows/nym-connect-publish-windows10.yml +++ b/.github/workflows/nym-connect-publish-windows10.yml @@ -15,8 +15,15 @@ jobs: fail-fast: false matrix: platform: [windows10] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - name: Clean up first continue-on-error: true @@ -39,10 +46,10 @@ jobs: Remove-Item -path certificate -include tempCert.txt Import-PfxCertificate -FilePath certificate/certificate.pfx -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText) - - name: Node v16 + - name: Node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Install Rust stable uses: actions-rs/toolchain@v1 @@ -50,15 +57,21 @@ jobs: toolchain: stable - name: Create env file - uses: timheuer/base64-to-file@v1.1 + uses: timheuer/base64-to-file@v1.2 with: fileName: '.env' encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }} + - name: Install project dependencies + shell: bash + run: cd .. && yarn --network-timeout 100000 + - name: Install app dependencies + shell: bash run: yarn --network-timeout 100000 - name: Build and sign it + shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ENABLE_CODE_SIGNING: ${{ secrets.WINDOWS_CERTIFICATE }} @@ -72,13 +85,41 @@ jobs: uses: actions/upload-artifact@v3 with: name: 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 + path: nym-connect/desktop/target/release/bundle/msi/nym-connect_1*_x64_en-US.msi retention-days: 30 - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: files: | nym-connect/desktop/target/release/bundle/msi/*.msi nym-connect/desktop/target/release/bundle/msi/*.msi.zip* + - id: release-info + name: Prepare release info + run: | + semver="${${{ github.ref_name }}##nym-connect-}" && semver="${semver##v}" + echo "version=$semver" >> "$GITHUB_OUTPUT" + echo "filename=nym-connect_$version_x64_en-US.msi" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/msi/nym-connect_*_x64_en-US.msi') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: NymConnect + category: connect + platform: Windows + secrets: inherit diff --git a/.github/workflows/nym-release-publish.yml b/.github/workflows/nym-release-publish.yml index a73eabfcb3..9252d7122d 100644 --- a/.github/workflows/nym-release-publish.yml +++ b/.github/workflows/nym-release-publish.yml @@ -2,17 +2,17 @@ name: Publish Nym binaries on: workflow_dispatch: - inputs: + inputs: add_tokio_unstable: description: 'True to add RUSTFLAGS="--cfg tokio_unstable"' required: true default: false - type: boolean + type: boolean release: types: [created] - + env: - NETWORK: mainnet + NETWORK: mainnet jobs: publish-nym: @@ -21,15 +21,33 @@ jobs: fail-fast: false matrix: platform: [custom-runner-linux] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + client_hash: ${{ steps.binary-hashes.outputs.client_hash }} + mixnode_hash: ${{ steps.binary-hashes.outputs.mixnode_hash }} + gateway_hash: ${{ steps.binary-hashes.outputs.gateway_hash }} + socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }} + netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }} + cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }} + netstat_hash: ${{ steps.binary-hashes.outputs.netstat_hash }} + client_version: ${{ steps.binary-versions.outputs.client_version }} + mixnode_version: ${{ steps.binary-versions.outputs.mixnode_version }} + gateway_version: ${{ steps.binary-versions.outputs.gateway_version }} + socks5_version: ${{ steps.binary-versions.outputs.socks5_version }} + netreq_version: ${{ steps.binary-versions.outputs.netreq_version }} + cli_version: ${{ steps.binary-versions.outputs.cli_version }} + netstat_version: ${{ steps.binary-versions.outputs.netstat_version }} + 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 + run: sudo apt-get update && sudo apt-get -y install ripgrep 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 @@ -62,7 +80,8 @@ jobs: target/release/nym-cli retention-days: 30 - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: @@ -76,3 +95,150 @@ jobs: target/release/nym-network-requester target/release/nym-network-statistics target/release/nym-cli + + - id: release-info + name: Prepare release info + run: | + semver="${${{ github.ref_name }}##nym-binaries-}" && semver="${semver##v}" + echo "version=$semver" >> "$GITHUB_OUTPUT" + + - id: binary-hashes + name: Generate binary hashes + run: | + echo "client_hash=${{ hashFiles('target/release/nym-client') }}" >> "$GITHUB_OUTPUT" + echo "mixnode_hash=${{ hashFiles('target/release/nym-mixnode') }}" >> "$GITHUB_OUTPUT" + echo "gateway_hash=${{ hashFiles('target/release/nym-gateway') }}" >> "$GITHUB_OUTPUT" + echo "socks5_hash=${{ hashFiles('target/release/nym-socks5-client') }}" >> "$GITHUB_OUTPUT" + echo "netreq_hash=${{ hashFiles('target/release/nym-network-requester') }}" >> "$GITHUB_OUTPUT" + echo "cli_hash=${{ hashFiles('target/release/nym-cli') }}" >> "$GITHUB_OUTPUT" + echo "netstat_hash=${{ hashFiles('target/release/nym-network-statistics') }}" >> "$GITHUB_OUTPUT" + + - id: binary-versions + name: Get binary versions + run: | + v=$(rg '^version = "(.*)"' -or '$1' clients/native/Cargo.toml) && echo "client_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' mixnode/Cargo.toml) && echo "mixnode_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' gateway/Cargo.toml) && echo "gateway_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' clients/socks5/Cargo.toml) && echo "socks5_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' service-providers/network-requester/Cargo.toml) && echo "netreq_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' tools/nym-cli/Cargo.toml) && echo "cli_version=$v" >> "$GITHUB_OUTPUT" + v=$(rg '^version = "(.*)"' -or '$1' service-providers/network-statistics/Cargo.toml) && echo "netstat_version=$v" >> "$GITHUB_OUTPUT" + + push-release-data-client: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.client_version }} + filename: nym-client + file_hash: ${{ needs.publish-nym.outputs.client_hash }} + name: Client + category: binaries + secrets: inherit + + push-release-data-mixnode: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.mixnode_version }} + filename: nym-mixnode + file_hash: ${{ needs.publish-nym.outputs.mixnode_hash }} + name: Mixnode + category: binaries + secrets: inherit + + push-release-data-gateway: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.gateway_version }} + filename: nym-gateway + file_hash: ${{ needs.publish-nym.outputs.gateway_hash }} + name: Gateway + category: binaries + secrets: inherit + + push-release-data-socks5: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.socks5_version }} + filename: nym-socks5-client + file_hash: ${{ needs.publish-nym.outputs.socks5_hash }} + name: Socks5 Client + category: binaries + secrets: inherit + + push-release-data-network-requester: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.netreq_version }} + filename: nym-network-requester + file_hash: ${{ needs.publish-nym.outputs.netreq_hash }} + name: Network Requester + category: binaries + secrets: inherit + + push-release-data-cli: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.cli_version }} + filename: nym-cli + file_hash: ${{ needs.publish-nym.outputs.cli_hash }} + name: Cli + category: binaries + secrets: inherit + + push-release-data-network-stat: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-nym + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-nym.outputs.release_id }} + release_date: ${{ needs.publish-nym.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md + version: ${{ needs.publish-nym.outputs.netstat_version }} + filename: nym-network-statistics + file_hash: ${{ needs.publish-nym.outputs.netstat_hash }} + name: Network Statistics + category: binaries + secrets: inherit diff --git a/.github/workflows/nym-wallet-publish-macos.yml b/.github/workflows/nym-wallet-publish-macos.yml index d3ef82759e..c21e99493d 100644 --- a/.github/workflows/nym-wallet-publish-macos.yml +++ b/.github/workflows/nym-wallet-publish-macos.yml @@ -15,15 +15,22 @@ jobs: fail-fast: false matrix: platform: [macos-latest] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - uses: actions/checkout@v2 - - name: Node v16 + - name: Node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Install Rust stable uses: actions-rs/toolchain@v1 with: @@ -51,11 +58,15 @@ jobs: security list-keychain -d user -s $KEYCHAIN_PATH - name: Create env file - uses: timheuer/base64-to-file@v1.1 + uses: timheuer/base64-to-file@v1.2 with: fileName: '.env' encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }} + - name: Install project dependencies + shell: bash + run: cd .. && yarn --network-timeout 100000 + - name: Install app dependencies and build it env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -80,11 +91,38 @@ jobs: if: ${{ always() }} run: | security delete-keychain $RUNNER_TEMP/app-signing.keychain-db - - - name: Upload to release based on tag name + - id: create-release + name: Upload to release based on tag name uses: softprops/action-gh-release@v1 if: github.event_name == 'release' with: files: | nym-wallet/target/release/bundle/dmg/*.dmg nym-wallet/target/release/bundle/macos/*.app.tar.gz* + - id: release-info + name: Prepare release info + run: | + semver="${${{ github.ref_name }}##nym-wallet-}" && semver="${semver##v}" + echo "version=$semver" >> "$GITHUB_OUTPUT" + echo "filename=nym-wallet_$version_x64.dmg" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/dmg/nym-wallet_*_x64.dmg') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet.app.tar.gz + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet.app.tar.gz.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: Wallet + category: wallet + platform: MacOS + secrets: inherit diff --git a/.github/workflows/nym-wallet-publish-ubuntu.yml b/.github/workflows/nym-wallet-publish-ubuntu.yml index 2b5a55c37a..5cfe79df98 100644 --- a/.github/workflows/nym-wallet-publish-ubuntu.yml +++ b/.github/workflows/nym-wallet-publish-ubuntu.yml @@ -1,5 +1,6 @@ name: Publish Nym Wallet (Ubuntu) on: + workflow_dispatch: release: types: [created] @@ -14,8 +15,15 @@ jobs: fail-fast: false matrix: platform: [custom-runner-linux] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - uses: actions/checkout@v2 @@ -25,19 +33,24 @@ jobs: sudo apt-get install -y webkit2gtk-4.0 continue-on-error: true - - name: Node v16 + - name: Node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Install Rust stable uses: actions-rs/toolchain@v1 with: toolchain: stable + + - name: Install project dependencies + shell: bash + run: cd .. && yarn --network-timeout 100000 + - name: Install app dependencies run: yarn - name: Create env file - uses: timheuer/base64-to-file@v1.1 + uses: timheuer/base64-to-file@v1.2 with: fileName: '.env' encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }} @@ -47,9 +60,40 @@ jobs: env: TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} - - name: Upload to release based on tag name + + - id: create-release + 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/appimage/*.AppImage nym-wallet/target/release/bundle/appimage/*.AppImage.tar.gz* + + - id: release-info + name: Prepare release info + run: | + semver="${${{ github.ref_name }}##nym-wallet-}" && semver="${semver##v}" + echo "version=$semver" >> "$GITHUB_OUTPUT" + echo "filename=nym-wallet_$version_amd64.AppImage" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/appimage/nym-wallet_*_amd64.AppImage') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: Wallet + category: wallet + platform: Ubuntu + secrets: inherit diff --git a/.github/workflows/nym-wallet-publish-windows10.yml b/.github/workflows/nym-wallet-publish-windows10.yml index 53411b7571..4ce6f38173 100644 --- a/.github/workflows/nym-wallet-publish-windows10.yml +++ b/.github/workflows/nym-wallet-publish-windows10.yml @@ -15,8 +15,15 @@ jobs: fail-fast: false matrix: platform: [windows10] - runs-on: ${{ matrix.platform }} + + outputs: + release_id: ${{ steps.create-release.outputs.id }} + release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }} + version: ${{ steps.release-info.outputs.version }} + filename: ${{ steps.release-info.outputs.filename }} + file_hash: ${{ steps.release-info.outputs.file_hash }} + steps: - name: Clean up first continue-on-error: true @@ -39,10 +46,10 @@ jobs: Remove-Item -path certificate -include tempCert.txt Import-PfxCertificate -FilePath certificate/certificate.pfx -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText) - - name: Node v16 + - name: Node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Install Rust stable uses: actions-rs/toolchain@v1 @@ -50,15 +57,21 @@ jobs: toolchain: stable - name: Create env file - uses: timheuer/base64-to-file@v1.1 + uses: timheuer/base64-to-file@v1.2 with: fileName: '.env' encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }} + - name: Install project dependencies + shell: bash + run: cd .. && yarn --network-timeout 100000 + - name: Install app dependencies + shell: bash run: yarn --network-timeout 100000 - name: Build and sign it + shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ENABLE_CODE_SIGNING: ${{ secrets.WINDOWS_CERTIFICATE }} @@ -75,10 +88,38 @@ jobs: path: nym-wallet/target/release/bundle/msi/nym-wallet_1.*.msi retention-days: 30 - - name: Upload to release based on tag name + - id: create-release + 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 nym-wallet/target/release/bundle/msi/*.msi.zip* + - id: release-info + name: Prepare release info + run: | + semver="${${{ github.ref_name }}##nym-wallet-}" && semver="${semver##v}" + echo "version=$semver" >> "$GITHUB_OUTPUT" + echo "filename=nym-wallet_$version_x64_en-US.msi" >> "$GITHUB_OUTPUT" + echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/msi/nym-wallet_*_x64_en-US.msi') }}" >> "$GITHUB_OUTPUT" + + push-release-data: + if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/push-release-data.yml + needs: publish-tauri + with: + release_tag: ${{ github.ref_name }} + release_id: ${{ needs.publish-tauri.outputs.release_id }} + release_date: ${{ needs.publish-tauri.outputs.release_date }} + download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }} + changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md + archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip + sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip.sig + version: ${{ needs.publish-tauri.outputs.version }} + filename: ${{ needs.publish-tauri.outputs.filename }} + file_hash: ${{ needs.publish-tauri.outputs.file_hash }} + name: Wallet + category: wallet + platform: Windows + secrets: inherit diff --git a/.github/workflows/nym-wallet-storybook.yml b/.github/workflows/nym-wallet-storybook.yml index e98fe5e338..54f5804cbf 100644 --- a/.github/workflows/nym-wallet-storybook.yml +++ b/.github/workflows/nym-wallet-storybook.yml @@ -16,7 +16,7 @@ jobs: - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Setup yarn run: npm install -g yarn - name: Install Rust stable @@ -24,11 +24,7 @@ jobs: with: toolchain: stable - name: Install wasm-pack - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - working-directory: clients/webassembly - - name: Build WASM - run: wasm-pack build - working-directory: clients/webassembly + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - name: Build dependencies run: yarn && yarn build - name: Build storybook diff --git a/.github/workflows/nym_wallet.yml b/.github/workflows/nym_wallet.yml index 4b921ffb7a..8c7f6fe267 100644 --- a/.github/workflows/nym_wallet.yml +++ b/.github/workflows/nym_wallet.yml @@ -34,10 +34,10 @@ jobs: profile: minimal toolchain: stable - - name: Node v16 + - name: Node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Install yarn for building application run: yarn install diff --git a/.github/workflows/push-release-data.yml b/.github/workflows/push-release-data.yml new file mode 100644 index 0000000000..db115af817 --- /dev/null +++ b/.github/workflows/push-release-data.yml @@ -0,0 +1,212 @@ +name: Push release data + +env: + strapi_download_url: 'https://strapi.feat-nym-update-nym-web.websites.dev.nymte.ch/api/downloaders' + strapi_updater_url: 'https://strapi.feat-nym-update-nym-web.websites.dev.nymte.ch/api/updaters' + +on: + workflow_call: + inputs: + release_tag: + required: true + description: Release tag + type: string + release_id: + required: true + description: Release ID + type: string + release_date: + required: true + description: Release date + type: string + download_base_url: + required: true + description: Download base URL + type: string + changelog_url: + required: true + description: Changelog URL + type: string + archive_url: + required: false + description: Binary archive URL + type: string + sig_url: + required: false + description: Archive signature URL + type: string + version: + required: true + description: Release version (semver) + type: string + filename: + required: true + description: Binary file name + type: string + file_hash: + required: true + description: Binary hash (sha256) + type: string + name: + required: true + description: Name + type: string + category: + required: true + description: Category + type: string + platform: + required: false + description: Platform + type: string + workflow_dispatch: + inputs: + # ⚠ since inputs are limited to 10 max for workflow_dispatch + # some properties were omitted + version: + required: true + description: Release version (semver) + type: string + default: '1.0.0' + release_id: + required: true + description: Release ID + type: string + default: '1234' + release_date: + required: true + description: Release date + type: string + default: '2023-06-26T10:09:16Z' + download_base_url: + required: true + description: Download base URL + type: string + default: 'https://github.com/nymtech/nym/releases/download/nym-wallet-v1.0.0' + changelog_url: + required: true + description: Changelog URL + type: string + default: 'https://github.com/nymtech/nym/blob/nym-wallet-v1.0.0/nym-wallet/CHANGELOG.md' + filename: + required: true + description: Binary file name + type: string + default: 'nym-wallet_1.0.0_amd64.AppImage' + file_hash: + required: true + description: Binary hash (sha256) + type: string + default: 'xxx' + name: + required: true + description: Name + type: string + default: 'Wallet' + category: + required: true + description: Category + default: 'wallet' + type: choice + options: + - wallet + - connect + - binaries + platform: + required: false + description: Platform + default: 'Ubuntu' + type: choice + options: + - Ubuntu + - Windows + - MacOS + +jobs: + push-download-data: + name: Push download data to Strapi + runs-on: custom-runner-linux + + steps: + - name: Release info + run: | + echo "version: ${{ inputs.version }}" + echo "tag: ${{ inputs.release_tag }}" + + - id: get_sig + name: Get sig + if: ${{ inputs.sig_url != null }} + run: | + output=$(curl -LsSf ${{ inputs.sig_url }}) + echo "sig=$output" >> "$GITHUB_OUTPUT" + - id: strapi-request + name: Strapi request + uses: fjogeleit/http-request-action@v1 + with: + url: ${{ env.strapi_download_url }} + method: 'POST' + bearerToken: ${{ secrets.STRAPI_API_TOKEN_RELEASES }} + customHeaders: '{"Content-Type": "application/json"}' + data: | + { + "data": { + "releaseId": "${{ inputs.release_id }}", + "releaseDate": "${{ inputs.release_date }}", + "downloadBaseUrl": "${{ inputs.download_base_url }}", + "changelogUrl": "${{ inputs.changelog_url }}", + "version": "${{ inputs.version }}", + "filename": "${{ inputs.filename }}", + "name": "${{ inputs.name }}", + "category": "${{ inputs.category }}", + "platform": "${{ inputs.platform }}", + "sha256": "${{ inputs.file_hash }}", + "sig": "${{ steps.get_sig.outputs.sig }}" + } + } + - name: Strapi Response + run: | + echo ${{ steps.strapi-request.outputs.response }} + + push-update-data: + name: Push update data to Strapi + runs-on: custom-runner-linux + # only push update data for tauri apps (desktop wallet and NC) + if: ${{ inputs.category == 'wallet' || inputs.category == 'connect' }} + + steps: + - name: Release info + run: | + echo "version: ${{ inputs.version }}" + echo "tag: ${{ inputs.release_tag }}" + - id: get_sig + name: Get sig + if: ${{ inputs.sig_url != null }} + run: | + output=$(curl -LsSf ${{ inputs.sig_url }}) + echo "sig=$output" >> "$GITHUB_OUTPUT" + - id: strapi-request + name: Strapi request + uses: fjogeleit/http-request-action@v1 + with: + url: ${{ env.strapi_updater_url }} + method: 'POST' + bearerToken: ${{ secrets.STRAPI_API_TOKEN_RELEASES }} + customHeaders: '{"Content-Type": "application/json"}' + data: | + { + "data": { + "releaseId": "${{ inputs.release_id }}", + "releaseDate": "${{ inputs.release_date }}", + "downloadUrl": "${{ inputs.archive_url }}", + "changelog": "See ${{ inputs.changelog_url }} for the changelog", + "version": "${{ inputs.version }}", + "filename": "${{ inputs.filename }}", + "category": "${{ inputs.category }}", + "platform": "${{ inputs.platform }}", + "sha256": "${{ inputs.file_hash }}", + "sig": "${{ steps.get_sig.outputs.sig }}" + } + } + - name: Strapi Response + run: | + echo ${{ steps.strapi-request.outputs.response }} diff --git a/.github/workflows/sdk-publish.yml b/.github/workflows/sdk-publish.yml new file mode 100644 index 0000000000..40b334e6ac --- /dev/null +++ b/.github/workflows/sdk-publish.yml @@ -0,0 +1,35 @@ +name: Publish SDK to NPM +on: + workflow_dispatch: + +jobs: + publish: + runs-on: [custom-runner-linux] + steps: + - uses: actions/checkout@v2 + + - name: Install Node + uses: actions/setup-node@v3 + with: + node-version: 18 + registry-url: 'https://registry.npmjs.org' + + - name: Setup yarn + run: npm install -g yarn + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Install dependencies + run: yarn + + - name: Build and publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} + working-directory: ./sdk/typescript/packages/sdk + run: scripts/publish.sh diff --git a/.github/workflows/support-files/.nvmrc b/.github/workflows/support-files/.nvmrc index b6a7d89c68..3c032078a4 100644 --- a/.github/workflows/support-files/.nvmrc +++ b/.github/workflows/support-files/.nvmrc @@ -1 +1 @@ -16 +18 diff --git a/.github/workflows/typescript-lint.yml b/.github/workflows/typescript-lint.yml index 347d9ea0f0..829ffb22e9 100644 --- a/.github/workflows/typescript-lint.yml +++ b/.github/workflows/typescript-lint.yml @@ -11,6 +11,7 @@ on: - 'nym-connect/mobile/package.json' - 'nym-wallet/src/**' - 'nym-wallet/package.json' + - 'explorer/**' pull_request: paths: - 'ts-packages/**' @@ -21,6 +22,7 @@ on: - 'nym-connect/mobile/package.json' - 'nym-wallet/src/**' - 'nym-wallet/package.json' + - 'explorer/**' jobs: build: @@ -33,19 +35,9 @@ jobs: - uses: rlespinasse/github-slug-action@v3.x - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 - name: Setup yarn run: npm install -g yarn - - name: Install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - name: Install wasm-pack - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - working-directory: clients/webassembly - - name: Build WASM - run: wasm-pack build - working-directory: clients/webassembly - name: Install run: yarn - name: Build packages diff --git a/CHANGELOG.md b/CHANGELOG.md index c12572b434..f938c81fe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,16 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] -## [1.1.23] (2023-06-27) +## [v1.1.23] (2023-07-04) - nym-cli: add client identity key signing support ([#3576]) +- Feature/node tester package ([#3634]) +- Add medium toggle to socks5 client ([#3615]) - Don't fully turn off background task when cover traffic is disabled ([#3596]) [#3576]: https://github.com/nymtech/nym/issues/3576 +[#3634]: https://github.com/nymtech/nym/pull/3634 +[#3615]: https://github.com/nymtech/nym/pull/3615 [#3596]: https://github.com/nymtech/nym/pull/3596 ## [v1.1.22] (2023-06-20) diff --git a/Cargo.lock b/Cargo.lock index eac214a1be..02b4791e57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5794,8 +5794,12 @@ name = "nym-name-service-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", "schemars", "serde", + "thiserror", ] [[package]] @@ -5845,6 +5849,7 @@ dependencies = [ "pretty_env_logger", "publicsuffix", "rand 0.7.3", + "regex", "reqwest", "serde", "serde_json", @@ -7719,13 +7724,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.1", + "regex-syntax 0.7.2", ] [[package]] @@ -7745,9 +7750,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "reqwest" diff --git a/README.md b/README.md index ddd904302f..fec13cd406 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr * nym-gateway - acts sort of like a mailbox for mixnet messages, which removes the need for direct delivery to potentially offline or firewalled devices. * nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing"). * nym-explorer - a (projected) block explorer and (existing) mixnet viewer. -* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework. +* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework. [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) [![Build Status](https://img.shields.io/github/actions/workflow/status/nymtech/nym/build.yml?branch=develop&style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop) @@ -21,7 +21,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr ### Building -Platform build instructions are available on [our docs site](https://nymtech.net/docs/binaries/build-nym.html). +Platform build instructions are available on [our docs site](https://nymtech.net/docs/binaries/pre-built-binaries.html). Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/wallet/desktop-wallet.html). ### Developing @@ -32,9 +32,9 @@ For Typescript components, please see [ts-packages](./ts-packages). ### Developer chat -> We used to use Keybase for developer chats, but we have since migrated to Matrix and Discord. We no longer check the old **nymtech.friends** Keybase team. +> We used to use Keybase for developer chats, but we have since migrated to Matrix and Discord. We no longer check the old **nymtech.friends** Keybase team. -You can chat to us in two places: +You can chat to us in two places: * The #dev channel on [Matrix](https://matrix.to/#/#dev:nymtech.chat) * The various developer channels on [Discord](https://discord.gg/nym) @@ -84,4 +84,3 @@ where `s'` is stake `s` scaled over total token circulating supply. ### Licensing and copyright information This program is available as open source under the terms of the Apache 2.0 license. However, some elements are being licensed under CC0-1.0 and MIT. For accurate information, please check individual files. - diff --git a/clients/credential/README.md b/clients/credential/README.md index e332e42555..9d82b4b839 100644 --- a/clients/credential/README.md +++ b/clients/credential/README.md @@ -15,9 +15,9 @@ The credential binary is still experimental software. The infrastructure for usi From the project's root directory, run: ``` -cargo build -p credential +cargo build -p nym-credential-client ``` -which generates the `credential` binary in `target/debug/credential`. +which generates the `nym-credential-client` binary in `target/debug/nym-credential-client`. ### Running @@ -25,7 +25,7 @@ which generates the `credential` binary in `target/debug/credential`. For example, you can get a credential worth 3 nym (3000000 unym) in a socks5 client that was already initialized like so: ``` -./target/debug/credential --config-env-file envs/sandbox.env --client-home-directory ~/.nym/socks5-clients/cred_client --nyxd-url https://sandbox-validator1.nymtech.net --mnemonic $MNEMONIC --recovery-dir /tmp/recovery --amount 3000000 +./target/debug/nym-credential-client --config-env-file envs/sandbox.env --client-home-directory ~/.nym/socks5-clients/cred_client --nyxd-url https://sandbox-validator1.nymtech.net --mnemonic $MNEMONIC --recovery-dir /tmp/recovery --amount 3000000 ``` More information regarding how to run the binary can be found by running it with the `--help` argument. diff --git a/clients/native/src/websocket/handler.rs b/clients/native/src/websocket/handler.rs index 3830a31894..3185d71900 100644 --- a/clients/native/src/websocket/handler.rs +++ b/clients/native/src/websocket/handler.rs @@ -117,10 +117,11 @@ impl Handler { let Ok(base_length) = self .lane_queue_lengths .lock() - .map(|guard| guard.get(&conn_lane).unwrap_or_default()) else { + .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 + return None; }; // get the number of pending replies waiting for reply surbs @@ -174,7 +175,7 @@ impl Handler { // Only reply back with a `LaneQueueLength` if the sender providided a connection id let TransmissionLane::ConnectionId(connection_id) = lane else { - return None + return None; }; self.get_lane_queue_length(connection_id).await @@ -206,7 +207,7 @@ impl Handler { // Only reply back with a `LaneQueueLength` if the sender providided a connection id let TransmissionLane::ConnectionId(connection_id) = lane else { - return None + return None; }; self.get_lane_queue_length(connection_id).await @@ -233,7 +234,7 @@ impl Handler { // Only reply back with a `LaneQueueLength` if the sender providided a connection id let TransmissionLane::ConnectionId(connection_id) = lane else { - return None + return None; }; self.get_lane_queue_length(connection_id).await diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 7c8fa68719..76337b84ac 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -94,6 +94,7 @@ impl From for OverrideConfig { use_anonymous_replies: init_config.use_reply_surbs, fastmode: init_config.fastmode, no_cover: init_config.no_cover, + medium_toggle: false, nyxd_urls: init_config.nyxd_urls, enabled_credentials_mode: init_config.enabled_credentials_mode, outfox: false, diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 266a4d6e26..2b5fe425b6 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -19,7 +19,7 @@ use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::config::GatewayEndpointConfig; use nym_client_core::error::ClientCoreError; use nym_config::OptionalSet; -use nym_sphinx::params::PacketType; +use nym_sphinx::params::{PacketSize, PacketType}; use std::error::Error; pub mod init; @@ -68,6 +68,7 @@ pub(crate) struct OverrideConfig { use_anonymous_replies: Option, fastmode: bool, no_cover: bool, + medium_toggle: bool, nyxd_urls: Option>, enabled_credentials_mode: Option, outfox: bool, @@ -86,6 +87,10 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box Config { + let disable_cover_traffic_with_keepalive = args.medium_toggle; + let secondary_packet_size = args.medium_toggle.then_some(PacketSize::ExtendedPacket16); + let no_per_hop_delays = args.medium_toggle; + let packet_type = if args.outfox { PacketType::Outfox } else { @@ -96,6 +101,17 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { BaseClientConfig::with_high_default_traffic_volume, args.fastmode, ) + .with_base( + // NOTE: This interacts with disabling cover traffic fully, so we want to this to be set before + BaseClientConfig::with_disabled_cover_traffic_with_keepalive, + disable_cover_traffic_with_keepalive, + ) + .with_base( + BaseClientConfig::with_secondary_packet_size, + secondary_packet_size, + ) + .with_base(BaseClientConfig::with_no_per_hop_delays, no_per_hop_delays) + // NOTE: see comment above about the order of the other disble cover traffic config .with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover) .with_base(BaseClientConfig::with_packet_type, packet_type) .with_optional(Config::with_anonymous_replies, args.use_anonymous_replies) diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index a6bc88344b..5d54038c06 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -60,6 +60,11 @@ pub(crate) struct Run { #[clap(long, hide = true)] no_cover: bool, + /// Enable medium mixnet traffic, for experiments only. + /// This includes things like disabling cover traffic, no per hop delays, etc. + #[clap(long, hide = true)] + medium_toggle: bool, + /// Set this client to work in a enabled credentials mode that would attempt to use gateway /// with bandwidth credential requirement. #[clap(long, hide = true)] @@ -77,6 +82,7 @@ impl From for OverrideConfig { use_anonymous_replies: run_config.use_anonymous_replies, fastmode: run_config.fastmode, no_cover: run_config.no_cover, + medium_toggle: run_config.medium_toggle, nyxd_urls: run_config.nyxd_urls, enabled_credentials_mode: run_config.enabled_credentials_mode, outfox: run_config.outfox, diff --git a/clients/validator/.nvmrc b/clients/validator/.nvmrc index 19c7bdba7b..25bf17fc5a 100644 --- a/clients/validator/.nvmrc +++ b/clients/validator/.nvmrc @@ -1 +1 @@ -16 \ No newline at end of file +18 \ No newline at end of file diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index f72332f6ec..811fbd10d6 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -2673,8 +2673,12 @@ name = "nym-name-service-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", "schemars", "serde", + "thiserror", ] [[package]] diff --git a/common/async-file-watcher/src/lib.rs b/common/async-file-watcher/src/lib.rs index 73faaa3dfa..e2ff23e830 100644 --- a/common/async-file-watcher/src/lib.rs +++ b/common/async-file-watcher/src/lib.rs @@ -101,7 +101,7 @@ impl AsyncFileWatcher { } let Some(filters) = &self.filters else { - return true + return true; }; for filter in filters { diff --git a/common/client-core/src/client/cover_traffic_stream.rs b/common/client-core/src/client/cover_traffic_stream.rs index 5e0a1408d9..8784bfb7ba 100644 --- a/common/client-core/src/client/cover_traffic_stream.rs +++ b/common/client-core/src/client/cover_traffic_stream.rs @@ -153,7 +153,7 @@ impl LoopCoverTrafficStream { fn loop_cover_message_size(&mut self) -> PacketSize { let Some(secondary_packet_size) = self.secondary_packet_size else { - return self.primary_packet_size + return self.primary_packet_size; }; let use_primary = self diff --git a/common/client-core/src/client/real_messages_control/message_handler.rs b/common/client-core/src/client/real_messages_control/message_handler.rs index b0846955c1..07482aa77f 100644 --- a/common/client-core/src/client/real_messages_control/message_handler.rs +++ b/common/client-core/src/client/real_messages_control/message_handler.rs @@ -224,7 +224,7 @@ where // if secondary packet was never set, then it's obvious we have to use the primary packet let Some(secondary_packet) = self.config.secondary_packet_size else { trace!("only primary packet size is available"); - return self.config.primary_packet_size + return self.config.primary_packet_size; }; let primary_count = diff --git a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs index 39c0e25f41..44c9810e70 100644 --- a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -200,7 +200,7 @@ where fn loop_cover_message_size(&mut self) -> PacketSize { let Some(secondary_packet_size) = self.config.traffic.secondary_packet_size else { - return self.config.traffic.primary_packet_size + return self.config.traffic.primary_packet_size; }; let use_primary = self diff --git a/common/client-core/src/client/replies/reply_controller/mod.rs b/common/client-core/src/client/replies/reply_controller/mod.rs index 0e5a5a0ae9..4ba9ce3ac9 100644 --- a/common/client-core/src/client/replies/reply_controller/mod.rs +++ b/common/client-core/src/client/replies/reply_controller/mod.rs @@ -727,7 +727,11 @@ where continue; } - let Some(last_received) = self.full_reply_storage.surbs_storage_ref().surbs_last_received_at(pending_reply_target) else { + 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.total_size()); to_remove.push(*pending_reply_target); continue; @@ -811,7 +815,8 @@ where // this should never ever happen (famous last words, eh?), but in case it DOES happen eventually // purge that malformed data - let Ok(sent_at) = OffsetDateTime::from_unix_timestamp(reply_key.sent_at_timestamp) else { + let Ok(sent_at) = OffsetDateTime::from_unix_timestamp(reply_key.sent_at_timestamp) + else { error!("somehow our stored timestamp ({}) for one of our reply key is corrupted!. Going to remove all the entry", reply_key.sent_at_timestamp); to_remove_keys.push(*digest); continue; diff --git a/common/client-core/src/client/transmission_buffer.rs b/common/client-core/src/client/transmission_buffer.rs index 65c0e23976..326cfa7f53 100644 --- a/common/client-core/src/client/transmission_buffer.rs +++ b/common/client-core/src/client/transmission_buffer.rs @@ -180,7 +180,7 @@ impl TransmissionBuffer { while items.len() < n { let Some(next) = self.pop_next_message_at_random(rng) else { - break + break; }; items.push(next) } diff --git a/common/client-libs/validator-client/src/nyxd/traits/name_service_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/name_service_query_client.rs index 95f0a2517e..0823d97d6a 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/name_service_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/name_service_query_client.rs @@ -4,7 +4,7 @@ use nym_contracts_common::ContractBuildInformation; use nym_name_service_common::{ msg::QueryMsg as NameQueryMsg, response::{ConfigResponse, NamesListResponse, PagedNamesListResponse}, - Address, NameEntry, NameId, + Address, NameId, RegisteredName, }; use serde::Deserialize; @@ -21,7 +21,7 @@ pub trait NameServiceQueryClient { .await } - async fn get_name_entry(&self, name_id: NameId) -> Result { + async fn get_name_entry(&self, name_id: NameId) -> Result { self.query_name_service_contract(NameQueryMsg::NameId { name_id }) .await } @@ -54,14 +54,14 @@ pub trait NameServiceQueryClient { .await } - async fn get_all_names(&self) -> Result, NyxdError> { + async fn get_all_names(&self) -> Result, NyxdError> { let mut services = Vec::new(); let mut start_after = None; loop { let mut paged_response = self.get_names_paged(start_after.take(), None).await?; - let last_id = paged_response.names.last().map(|serv| serv.name_id); + let last_id = paged_response.names.last().map(|serv| serv.id); services.append(&mut paged_response.names); if let Some(start_after_res) = last_id { diff --git a/common/client-libs/validator-client/src/nyxd/traits/name_service_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/name_service_signing_client.rs index 4e4e43ee77..1235043094 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/name_service_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/name_service_signing_client.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use async_trait::async_trait; -use nym_name_service_common::{msg::ExecuteMsg as NameExecuteMsg, Address, NameId, NymName}; +use nym_contracts_common::signing::MessageSignature; +use nym_name_service_common::{msg::ExecuteMsg as NameExecuteMsg, NameDetails, NameId, NymName}; use crate::nyxd::{ coin::Coin, cosmwasm_client::types::ExecuteResult, error::NyxdError, Fee, NyxdClient, @@ -20,14 +21,17 @@ pub trait NameServiceSigningClient { async fn register_name( &self, - name: NymName, - address: Address, + name: NameDetails, + owner_signature: MessageSignature, deposit: Coin, fee: Option, ) -> Result { self.execute_name_service_contract( fee, - NameExecuteMsg::Register { name, address }, + NameExecuteMsg::Register { + name, + owner_signature, + }, vec![deposit], ) .await diff --git a/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs b/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs index 735832be08..a305df4839 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/families/create_family_join_permit_sign_payload.rs @@ -35,9 +35,14 @@ pub async fn create_family_join_permit_sign_payload(args: Args, client: QueryCli info!("Create family join permit sign payload"); // get the address of our mixnode to recover the family head information - let Some(mixnode) = client.get_owned_mixnode(&args.address).await.unwrap().mixnode_details else { + let Some(mixnode) = client + .get_owned_mixnode(&args.address) + .await + .unwrap() + .mixnode_details + else { eprintln!("{} does not seem to even own a mixnode!", args.address); - return + return; }; // make sure this mixnode is actually a family head diff --git a/common/commands/src/validator/mixnet/operators/name/register.rs b/common/commands/src/validator/mixnet/operators/name/register.rs index 6458faf8a6..e246e4241a 100644 --- a/common/commands/src/validator/mixnet/operators/name/register.rs +++ b/common/commands/src/validator/mixnet/operators/name/register.rs @@ -1,6 +1,7 @@ use clap::Parser; use log::{error, info}; -use nym_name_service_common::{Address, Coin, NymName}; +use nym_contracts_common::signing::MessageSignature; +use nym_name_service_common::{Address, Coin, NameDetails, NymName}; use nym_validator_client::nyxd::{error::NyxdError, traits::NameServiceSigningClient}; use tap::TapFallible; @@ -16,9 +17,15 @@ pub struct Args { #[clap(long)] pub nym_address: String, + #[clap(long)] + pub signature: MessageSignature, + /// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym') #[clap(long)] pub deposit: u128, + + #[clap(long)] + pub identity_key: String, } pub async fn register(args: Args, client: SigningClient) -> Result<(), NyxdError> { @@ -29,12 +36,17 @@ pub async fn register(args: Args, client: SigningClient) -> Result<(), NyxdError let name = NymName::new(&args.name).expect("invalid name"); let address = Address::new(&args.nym_address); + let name = NameDetails { + name, + address, + identity_key: args.identity_key, + }; let denom = client.current_chain_details().mix_denom.base.as_str(); let deposit = Coin::new(args.deposit, denom); let res = client - .register_name(name, address, deposit.into(), None) + .register_name(name, args.signature, deposit.into(), None) .await .tap_err(|err| error!("Failed to register name: {err:#?}"))?; diff --git a/common/commands/src/validator/mixnet/query/query_all_names.rs b/common/commands/src/validator/mixnet/query/query_all_names.rs index e3bb24ebae..5a17cad30d 100644 --- a/common/commands/src/validator/mixnet/query/query_all_names.rs +++ b/common/commands/src/validator/mixnet/query/query_all_names.rs @@ -34,8 +34,8 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) { table.set_header(vec!["Name Id", "Owner", "Nym Address", "Name"]); for name_entry in res.names { table.add_row(vec![ - name_entry.name_id.to_string(), - name_entry.name.owner.to_string(), + name_entry.id.to_string(), + name_entry.owner.to_string(), name_entry.name.address.to_string(), name_entry.name.name.to_string(), ]); diff --git a/common/cosmwasm-smart-contracts/name-service/Cargo.toml b/common/cosmwasm-smart-contracts/name-service/Cargo.toml index 46b767a3df..23fb221e23 100644 --- a/common/cosmwasm-smart-contracts/name-service/Cargo.toml +++ b/common/cosmwasm-smart-contracts/name-service/Cargo.toml @@ -7,5 +7,9 @@ edition = "2021" [dependencies] cosmwasm-std = { workspace = true } +cw-controllers = { workspace = true } +cw-utils = { workspace = true } +nym-contracts-common = { path = "../contracts-common", version = "0.5.0" } schemars = "0.8" serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } diff --git a/contracts/name-service/src/error.rs b/common/cosmwasm-smart-contracts/name-service/src/error.rs similarity index 71% rename from contracts/name-service/src/error.rs rename to common/cosmwasm-smart-contracts/name-service/src/error.rs index 66ca6485dd..8c31308b66 100644 --- a/contracts/name-service/src/error.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/error.rs @@ -1,8 +1,10 @@ use cosmwasm_std::{Addr, StdError}; use cw_controllers::AdminError; -use nym_name_service_common::{Address, NameId, NymName}; +use nym_contracts_common::signing::verifier::ApiVerifierError; use thiserror::Error; +use crate::{Address, NameId, NymName}; + #[derive(Error, Debug, PartialEq)] pub enum NameServiceError { #[error("{0}")] @@ -47,6 +49,21 @@ pub enum NameServiceError { error_message: String, }, + #[error("Failed to recover ed25519 public key from its base58 representation - {0}")] + MalformedEd25519IdentityKey(String), + + #[error("Failed to recover ed25519 signature from its base58 representation - {0}")] + MalformedEd25519Signature(String), + + #[error("Provided ed25519 signature did not verify correctly")] + InvalidEd25519Signature, + + #[error("failed to verify message signature: {source}")] + SignatureVerificationFailure { + #[from] + source: ApiVerifierError, + }, + #[error("duplicate entries detected for name: {name}")] DuplicateNames { name: NymName }, @@ -54,4 +71,4 @@ pub enum NameServiceError { NameAlreadyRegistered { name: NymName }, } -pub(crate) type Result = std::result::Result; +pub type Result = std::result::Result; diff --git a/common/cosmwasm-smart-contracts/name-service/src/events.rs b/common/cosmwasm-smart-contracts/name-service/src/events.rs index 0454a8fb28..5bf65e606d 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/events.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/events.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Coin, Event}; -use crate::{NameId, RegisteredName}; +use crate::RegisteredName; pub enum NameEventType { Register, @@ -34,29 +34,29 @@ pub const OWNER: &str = "owner"; pub const DEPOSIT_REQUIRED: &str = "deposit_required"; -pub fn new_register_event(name_id: NameId, name: RegisteredName) -> Event { +pub fn new_register_event(name: RegisteredName) -> Event { Event::new(NameEventType::Register) .add_attribute(ACTION, NameEventType::Register) - .add_attribute(NAME_ID, name_id.to_string()) - .add_attribute(NAME, name.name.to_string()) - .add_attribute(name.address.event_tag(), name.address.to_string()) + .add_attribute(NAME_ID, name.id.to_string()) + .add_attribute(NAME, name.name.name.to_string()) + .add_attribute(name.name.address.event_tag(), name.name.address.to_string()) .add_attribute(OWNER, name.owner.to_string()) } -pub fn new_delete_id_event(name_id: NameId, name: RegisteredName) -> Event { +pub fn new_delete_id_event(name: RegisteredName) -> Event { Event::new(NameEventType::DeleteId) .add_attribute(ACTION, NameEventType::DeleteId) - .add_attribute(NAME_ID, name_id.to_string()) - .add_attribute(NAME, name.name.to_string()) - .add_attribute(name.address.event_tag(), name.address.to_string()) + .add_attribute(NAME_ID, name.id.to_string()) + .add_attribute(NAME, name.name.name.to_string()) + .add_attribute(name.name.address.event_tag(), name.name.address.to_string()) } -pub fn new_delete_name_event(name_id: NameId, name: RegisteredName) -> Event { +pub fn new_delete_name_event(name: RegisteredName) -> Event { Event::new(NameEventType::DeleteId) .add_attribute(ACTION, NameEventType::DeleteName) - .add_attribute(NAME_ID, name_id.to_string()) - .add_attribute(NAME, name.name.to_string()) - .add_attribute(name.address.event_tag(), name.address.to_string()) + .add_attribute(NAME_ID, name.id.to_string()) + .add_attribute(NAME, name.name.name.to_string()) + .add_attribute(name.name.address.event_tag(), name.name.address.to_string()) } pub fn new_update_deposit_required_event(deposit_required: Coin) -> Event { diff --git a/common/cosmwasm-smart-contracts/name-service/src/lib.rs b/common/cosmwasm-smart-contracts/name-service/src/lib.rs index 4609473c60..71a702b7c1 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/lib.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/lib.rs @@ -1,6 +1,8 @@ +pub mod error; pub mod events; pub mod msg; pub mod response; +pub mod signing_types; pub mod types; // Re-export all types at the top-level diff --git a/common/cosmwasm-smart-contracts/name-service/src/msg.rs b/common/cosmwasm-smart-contracts/name-service/src/msg.rs index 6a6698f34d..9a8e2b8ecf 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/msg.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/msg.rs @@ -1,5 +1,6 @@ -use crate::{Address, NameId, NymName}; +use crate::{Address, NameDetails, NameId, NymName}; use cosmwasm_std::Coin; +use nym_contracts_common::signing::MessageSignature; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] @@ -22,7 +23,10 @@ pub struct MigrateMsg {} #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { /// Announcing a name pointing to a nym-address - Register { name: NymName, address: Address }, + Register { + name: NameDetails, + owner_signature: MessageSignature, + }, /// Delete a name entry by id DeleteId { name_id: NameId }, /// Delete a name entry by name @@ -38,8 +42,11 @@ impl ExecuteMsg { pub fn default_memo(&self) -> String { match self { - ExecuteMsg::Register { name, address } => { - format!("registering {address} as name: {name}") + ExecuteMsg::Register { + name, + owner_signature: _, + } => { + format!("registering {} as name: {}", name.address, name.name) } ExecuteMsg::DeleteId { name_id } => { format!("deleting name with id {name_id}") @@ -75,6 +82,9 @@ pub enum QueryMsg { limit: Option, start_after: Option, }, + SigningNonce { + address: String, + }, Config {}, GetContractVersion {}, #[serde(rename = "get_cw2_contract_version")] diff --git a/common/cosmwasm-smart-contracts/name-service/src/response.rs b/common/cosmwasm-smart-contracts/name-service/src/response.rs index 5daa630d01..8087195eee 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/response.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/response.rs @@ -1,36 +1,22 @@ -use crate::{msg::ExecuteMsg, NameEntry, NameId, RegisteredName}; +use crate::{NameId, RegisteredName}; use cosmwasm_std::Coin; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// Like [`NameEntry`] but since it's a response type the name is an option depending on if -/// the name exists or not. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -#[serde(rename_all = "snake_case")] -pub struct NameEntryResponse { - pub name_id: NameId, - pub name: Option, -} - #[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct NamesListResponse { - pub names: Vec, + pub names: Vec, } impl NamesListResponse { - pub fn new(names: Vec<(NameId, RegisteredName)>) -> NamesListResponse { - NamesListResponse { - names: names - .into_iter() - .map(|(name_id, name)| NameEntry::new(name_id, name)) - .collect(), - } + pub fn new(names: Vec) -> NamesListResponse { + NamesListResponse { names } } } -impl From<&[NameEntry]> for NamesListResponse { - fn from(names: &[NameEntry]) -> Self { +impl From<&[RegisteredName]> for NamesListResponse { + fn from(names: &[RegisteredName]) -> Self { NamesListResponse { names: names.to_vec(), } @@ -40,21 +26,17 @@ impl From<&[NameEntry]> for NamesListResponse { #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] #[serde(rename_all = "snake_case")] pub struct PagedNamesListResponse { - pub names: Vec, + pub names: Vec, pub per_page: usize, pub start_next_after: Option, } impl PagedNamesListResponse { pub fn new( - names: Vec<(NameId, RegisteredName)>, + names: Vec, per_page: usize, start_next_after: Option, ) -> PagedNamesListResponse { - let names = names - .into_iter() - .map(|(name_id, name)| NameEntry::new(name_id, name)) - .collect(); PagedNamesListResponse { names, per_page, @@ -68,12 +50,3 @@ impl PagedNamesListResponse { pub struct ConfigResponse { pub deposit_required: Coin, } - -impl From for ExecuteMsg { - fn from(name: RegisteredName) -> Self { - ExecuteMsg::Register { - name: name.name, - address: name.address, - } - } -} diff --git a/common/cosmwasm-smart-contracts/name-service/src/signing_types.rs b/common/cosmwasm-smart-contracts/name-service/src/signing_types.rs new file mode 100644 index 0000000000..80a19a7e3d --- /dev/null +++ b/common/cosmwasm-smart-contracts/name-service/src/signing_types.rs @@ -0,0 +1,32 @@ +use cosmwasm_std::{Addr, Coin}; +use nym_contracts_common::signing::{ + ContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose, +}; +use serde::Serialize; + +use crate::NameDetails; + +pub type SignableNameRegisterMsg = SignableMessage>; + +#[derive(Serialize)] +pub struct NameRegister { + name: NameDetails, +} + +impl SigningPurpose for NameRegister { + fn message_type() -> MessageType { + MessageType::new("name-register") + } +} + +pub fn construct_name_register_sign_payload( + nonce: Nonce, + sender: Addr, + deposit: Coin, + name: NameDetails, +) -> SignableNameRegisterMsg { + let payload = NameRegister { name }; + let proxy = None; + let content = ContractMessageContent::new(sender, proxy, vec![deposit], payload); + SignableMessage::new(nonce, content) +} diff --git a/common/cosmwasm-smart-contracts/name-service/src/types.rs b/common/cosmwasm-smart-contracts/name-service/src/types.rs index 166e095a2f..dd44340815 100644 --- a/common/cosmwasm-smart-contracts/name-service/src/types.rs +++ b/common/cosmwasm-smart-contracts/name-service/src/types.rs @@ -1,24 +1,48 @@ use std::fmt::{Display, Formatter}; use cosmwasm_std::{Addr, Coin}; +use nym_contracts_common::IdentityKey; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// The directory of services are indexed by [`ServiceId`]. +/// The directory of names are indexed by [`NameId`]. pub type NameId = u32; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)] pub struct RegisteredName { + /// Unique id assigned to the registerd name. + pub id: NameId, + + /// The registerd name details. + pub name: NameDetails, + + /// name owner. + pub owner: Addr, + + /// Block height at which the name was added. + pub block_height: u64, + + /// The deposit used to announce the name. + pub deposit: Coin, +} + +impl RegisteredName { + // Shortcut for getting the actual name + pub fn entry(&self) -> &NymName { + &self.name.name + } +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)] +pub struct NameDetails { /// The name pointing to the nym address pub name: NymName, - /// The address of the service. + + /// The address of the name alias. pub address: Address, - /// Service owner. - pub owner: Addr, - /// Block height at which the service was added. - pub block_height: u64, - /// The deposit used to announce the service. - pub deposit: Coin, + + /// The identity key of the registered name. + pub identity_key: IdentityKey, } /// String representation of a nym address, which is of the form @@ -68,6 +92,7 @@ pub enum NymNameError { InvalidName, } +/// Defines what names are allowed fn is_valid_name_char(c: char) -> bool { // Normal lowercase letters (c.is_alphabetic() && c.is_lowercase()) @@ -98,20 +123,6 @@ impl Display for NymName { } } -/// [`RegisterdName`] together with the assigned [`NameId`]. -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct NameEntry { - pub name_id: NameId, - pub name: RegisteredName, -} - -impl NameEntry { - pub fn new(name_id: NameId, name: RegisteredName) -> Self { - Self { name_id, name } - } -} - #[cfg(test)] mod tests { use super::NymName; diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml b/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml index 67185a593c..3965734b2d 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml +++ b/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml @@ -7,9 +7,9 @@ edition = "2021" [dependencies] cosmwasm-std = { workspace = true } +cw-controllers = { workspace = true } +cw-utils = { workspace = true } nym-contracts-common = { path = "../contracts-common", version = "0.5.0" } schemars = "0.8" serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } -cw-utils = { workspace = true } -cw-controllers = { workspace = true } diff --git a/common/node-tester-utils/src/tester.rs b/common/node-tester-utils/src/tester.rs index 0b807c54f2..6577ef61f5 100644 --- a/common/node-tester-utils/src/tester.rs +++ b/common/node-tester-utils/src/tester.rs @@ -154,7 +154,7 @@ where T: Serialize + Clone, { let Some(node) = self.base_topology.find_mix(mix_id) else { - return Err(NetworkTestingError::NonExistentMixnode {mix_id}) + return Err(NetworkTestingError::NonExistentMixnode { mix_id }); }; self.mixnode_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient) @@ -170,8 +170,13 @@ where where T: Serialize + Clone, { - let Some(node) = self.base_topology.find_mix_by_identity(&encoded_mix_identity) else { - return Err(NetworkTestingError::NonExistentMixnodeIdentity { mix_identity: encoded_mix_identity }) + let Some(node) = self + .base_topology + .find_mix_by_identity(&encoded_mix_identity) + else { + return Err(NetworkTestingError::NonExistentMixnodeIdentity { + mix_identity: encoded_mix_identity, + }); }; self.mixnode_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient) @@ -212,7 +217,9 @@ where T: Serialize + Clone, { let Some(node) = self.base_topology.find_gateway(&encoded_gateway_identity) else { - return Err(NetworkTestingError::NonExistentGateway { gateway_identity: encoded_gateway_identity }) + return Err(NetworkTestingError::NonExistentGateway { + gateway_identity: encoded_gateway_identity, + }); }; self.gateway_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient) diff --git a/common/socks5-client-core/src/socks/request.rs b/common/socks5-client-core/src/socks/request.rs index 525391c9ab..db222bdc20 100644 --- a/common/socks5-client-core/src/socks/request.rs +++ b/common/socks5-client-core/src/socks/request.rs @@ -107,7 +107,7 @@ impl SocksRequest { // ATYP let Some(addr_type) = AddrType::from(packet[3] as usize) else { error!("No Addr"); - return Err(ResponseCodeV5::AddrTypeNotSupported.into()) + return Err(ResponseCodeV5::AddrTypeNotSupported.into()); }; // DST.ADDR diff --git a/common/store-cipher/src/lib.rs b/common/store-cipher/src/lib.rs index b5bf471b8f..98d586151f 100644 --- a/common/store-cipher/src/lib.rs +++ b/common/store-cipher/src/lib.rs @@ -190,7 +190,7 @@ impl StoreCipher { exported.ciphertext_info.ciphertext(), ) else { key.zeroize(); - return Err(Error::InvalidImportPassphrase) + return Err(Error::InvalidImportPassphrase); }; // if we successfully decrypted aes256gcm ciphertext, it's almost certainly correct diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 962c65f2e7..7f0d6f8a13 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1361,6 +1361,7 @@ name = "nym-name-service" version = "0.1.0" dependencies = [ "anyhow", + "bs58", "cosmwasm-std", "cw-controllers", "cw-multi-test", @@ -1368,8 +1369,10 @@ dependencies = [ "cw-utils", "cw2", "nym-contracts-common", + "nym-crypto", "nym-name-service-common", "rand 0.8.5", + "rand_chacha 0.2.2", "rstest", "semver", "serde", @@ -1382,8 +1385,12 @@ name = "nym-name-service-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", "schemars", "serde", + "thiserror", ] [[package]] @@ -1566,9 +1573,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.52" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] diff --git a/contracts/mixnet/src/families/signature_helpers.rs b/contracts/mixnet/src/families/signature_helpers.rs index 186bbc19bc..222c7548cd 100644 --- a/contracts/mixnet/src/families/signature_helpers.rs +++ b/contracts/mixnet/src/families/signature_helpers.rs @@ -24,8 +24,12 @@ pub(crate) fn verify_family_join_permit( let Some(head_mixnode) = mixnodes_storage::mixnode_bonds() .idx .identity_key - .item(deps.storage, granter.identity().to_owned())?.map(|record| record.1) else { - return Err(MixnetContractError::FamilyDoesNotExist { head: granter.identity().to_string() }) + .item(deps.storage, granter.identity().to_owned())? + .map(|record| record.1) + else { + return Err(MixnetContractError::FamilyDoesNotExist { + head: granter.identity().to_string(), + }); }; let nonce = signing_storage::get_signing_nonce(deps.storage, head_mixnode.owner)?; let msg = construct_family_join_permit(nonce, granter, proxy, member.to_owned()); diff --git a/contracts/name-service/Cargo.toml b/contracts/name-service/Cargo.toml index 7ddb660d56..56fe0145cf 100644 --- a/contracts/name-service/Cargo.toml +++ b/contracts/name-service/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] +bs58 = "0.4.0" cosmwasm-std = { workspace = true } cw-controllers = { workspace = true } cw-storage-plus = { workspace = true } @@ -24,5 +25,7 @@ vergen = { version = "=7.4.3", default-features = false, features = ["build", "g [dev-dependencies] anyhow = "1.0.40" cw-multi-test = { workspace = true } +nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } rand = "0.8.5" +rand_chacha = "0.2" rstest = "0.17.0" diff --git a/contracts/name-service/src/constants.rs b/contracts/name-service/src/constants.rs index 6ac0998ae0..0b87f40264 100644 --- a/contracts/name-service/src/constants.rs +++ b/contracts/name-service/src/constants.rs @@ -14,3 +14,5 @@ pub const NAMES_PK_NAMESPACE: &str = "nanames"; pub const NAMES_OWNER_IDX_NAMESPACE: &str = "naowner"; pub const NAMES_ADDRESS_IDX_NAMESPACE: &str = "naaddress"; pub const NAMES_NAME_IDX_NAMESPACE: &str = "naname"; + +pub const SIGNING_NONCES_NAMESPACE: &str = "nasn"; diff --git a/contracts/name-service/src/contract.rs b/contracts/name-service/src/contract.rs index a88d831dc8..f3347c4d6a 100644 --- a/contracts/name-service/src/contract.rs +++ b/contracts/name-service/src/contract.rs @@ -1,6 +1,6 @@ use crate::{ - error::{NameServiceError, Result}, state::{self, Config}, + NameServiceError, Result, }; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; use nym_name_service_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; @@ -72,7 +72,10 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Register { name, address } => execute::register(deps, env, info, name, address), + ExecuteMsg::Register { + name, + owner_signature, + } => execute::register(deps, env, info, name, owner_signature), ExecuteMsg::DeleteId { name_id } => execute::delete_id(deps, info, name_id), ExecuteMsg::DeleteName { name } => execute::delete_name(deps, info, name), ExecuteMsg::UpdateDepositRequired { deposit_required } => { @@ -90,6 +93,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { QueryMsg::All { limit, start_after } => { to_binary(&query::query_all_paged(deps, limit, start_after)?) } + QueryMsg::SigningNonce { address } => { + to_binary(&query::query_current_signing_nonce(deps, address)?) + } QueryMsg::Config {} => to_binary(&query::query_config(deps)?), QueryMsg::GetContractVersion {} => to_binary(&query::query_contract_version()), QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?), @@ -102,16 +108,19 @@ mod tests { use super::*; use crate::test_helpers::{ - assert::{assert_config, assert_empty, assert_name, assert_names, assert_not_found}, - fixture::name_fixture, - helpers::{get_attribute, nyms}, + assert::{ + assert_config, assert_current_nonce, assert_empty, assert_name, assert_names, + assert_not_found, + }, + fixture::new_name_details_with_sign, + helpers::{get_attribute, nyms, test_rng}, }; use cosmwasm_std::{ testing::{mock_dependencies, mock_env, mock_info}, Addr, Coin, }; - use nym_name_service_common::{msg::ExecuteMsg, NameEntry, NameId}; + use nym_name_service_common::{msg::ExecuteMsg, NameId, RegisteredName}; const DENOM: &str = "unym"; @@ -135,23 +144,28 @@ mod tests { } #[test] - fn register_fails_incorrect_deposit() { + fn register_fails_deposit_too_small() { + let mut rng = test_rng(); let mut deps = mock_dependencies(); let msg = InstantiateMsg::new(nyms(100)); let info = mock_info("creator", &[]); - let admin = info.sender.clone(); let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(res.messages.len(), 0); - // Register - let msg: ExecuteMsg = name_fixture().into(); - let owner = name_fixture().owner.to_string(); + let deposit = nyms(99); + let owner = "steve"; + let (name, owner_signature) = + new_name_details_with_sign(deps.as_mut(), &mut rng, "foo", "address", owner, deposit); + let msg = ExecuteMsg::Register { + name, + owner_signature, + }; assert_eq!( execute( deps.as_mut(), mock_env(), - mock_info(&owner, &[nyms(99)]), + mock_info(owner, &[nyms(99)]), msg.clone() ) .unwrap_err(), @@ -161,34 +175,135 @@ mod tests { } ); + // Since we signed for 99unym deposit. assert_eq!( execute( deps.as_mut(), mock_env(), - mock_info(&owner, &[nyms(101)]), + mock_info(owner, &[nyms(100)]), msg ) .unwrap_err(), + NameServiceError::InvalidEd25519Signature, + ); + } + + #[test] + fn register_fails_deposit_too_large() { + let mut rng = test_rng(); + let mut deps = mock_dependencies(); + let msg = InstantiateMsg::new(nyms(100)); + let info = mock_info("creator", &[]); + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(res.messages.len(), 0); + + let deposit = nyms(101); + let owner = "steve"; + let (name, owner_signature) = + new_name_details_with_sign(deps.as_mut(), &mut rng, "foo", "address", owner, deposit); + let msg = ExecuteMsg::Register { + name, + owner_signature, + }; + + assert_eq!( + execute( + deps.as_mut(), + mock_env(), + mock_info(owner, &[nyms(101)]), + msg.clone() + ) + .unwrap_err(), NameServiceError::TooLargeDeposit { funds: 101u128.into(), deposit_required: 100u128.into(), } ); - assert_config(deps.as_ref(), &admin, Coin::new(100, DENOM)); - assert_empty(deps.as_ref()); + // Since we signed for 101unym deposit. + assert_eq!( + execute( + deps.as_mut(), + mock_env(), + mock_info(owner, &[nyms(100)]), + msg + ) + .unwrap_err(), + NameServiceError::InvalidEd25519Signature, + ); } #[test] - fn register_success() { + fn register_fails_owner_mismatch() { + let mut rng = test_rng(); let mut deps = mock_dependencies(); let msg = InstantiateMsg::new(nyms(100)); let info = mock_info("creator", &[]); let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(res.messages.len(), 0); + // Setup + let deposit = nyms(100); + let owner = "steve"; + let (name, owner_signature) = new_name_details_with_sign( + deps.as_mut(), + &mut rng, + "my-name", + "my-address", + owner, + deposit, + ); + // Register - let msg: ExecuteMsg = name_fixture().into(); + let msg = ExecuteMsg::Register { + name, + owner_signature, + }; + assert_eq!( + execute( + deps.as_mut(), + mock_env(), + mock_info("timmy", &[nyms(100)]), + msg.clone(), + ) + .unwrap_err(), + NameServiceError::InvalidEd25519Signature, + ); + assert!(execute( + deps.as_mut(), + mock_env(), + mock_info("steve", &[nyms(100)]), + msg + ) + .is_ok()); + } + + #[test] + fn register_success() { + let mut rng = test_rng(); + let mut deps = mock_dependencies(); + let msg = InstantiateMsg::new(nyms(100)); + let info = mock_info("creator", &[]); + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(res.messages.len(), 0); + + // Setup + let deposit = nyms(100); + let owner = "steve"; + let (name, owner_signature) = new_name_details_with_sign( + deps.as_mut(), + &mut rng, + "my-name", + "my-address", + owner, + deposit.clone(), + ); + + // Register + let msg = ExecuteMsg::Register { + name: name.clone(), + owner_signature, + }; let info = mock_info("steve", &[nyms(100)]); let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); @@ -198,17 +313,24 @@ mod tests { assert_eq!(id, expected_id); assert_eq!( get_attribute(&res, "register", "name"), - "my-service".to_string() + "my-name".to_string() ); assert_eq!( get_attribute(&res, "register", "nym_address"), - "client_id.client_key@gateway_id".to_string() + "my-address".to_string() ); + // Check that the nonce has been incremented, but only for the owner + assert_current_nonce(deps.as_ref(), &Addr::unchecked("steve"), 1); + assert_current_nonce(deps.as_ref(), &Addr::unchecked("timmy"), 0); + // The expected registered name - let expected_name = NameEntry { - name_id: expected_id, - name: name_fixture(), + let expected_name = RegisteredName { + id: expected_id, + name, + owner: Addr::unchecked(owner), + block_height: 12345, + deposit, }; assert_names(deps.as_ref(), &[expected_name.clone()]); assert_name(deps.as_ref(), &expected_name); @@ -216,6 +338,7 @@ mod tests { #[test] fn delete() { + let mut rng = test_rng(); let mut deps = mock_dependencies(); let msg = InstantiateMsg::new(Coin::new(100, "unym")); let info = mock_info("creator", &[]); @@ -223,16 +346,31 @@ mod tests { assert_eq!(res.messages.len(), 0); // Register - let msg: ExecuteMsg = name_fixture().into(); + let deposit = nyms(100); + let steve = "steve"; + let (name, owner_signature) = new_name_details_with_sign( + deps.as_mut(), + &mut rng, + "my-name", + "my-address", + steve, + deposit.clone(), + ); + let msg = ExecuteMsg::Register { + name: name.clone(), + owner_signature, + }; let info_steve = mock_info("steve", &[nyms(100)]); - assert_eq!(info_steve.sender, name_fixture().owner); - execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap(); + execute(deps.as_mut(), mock_env(), info_steve.clone(), msg).unwrap(); // The expected registerd name let expected_id = 1; - let expected_name = NameEntry { - name_id: expected_id, - name: name_fixture(), + let expected_name = RegisteredName { + id: expected_id, + name, + owner: Addr::unchecked(steve), + block_height: 12345, + deposit, }; assert_names(deps.as_ref(), &[expected_name]); @@ -248,12 +386,8 @@ mod tests { // Removing an non-existent name will fail let msg = ExecuteMsg::delete_id(expected_id + 1); - let info_owner = MessageInfo { - sender: name_fixture().owner, - funds: vec![], - }; assert_eq!( - execute(deps.as_mut(), mock_env(), info_owner.clone(), msg).unwrap_err(), + execute(deps.as_mut(), mock_env(), info_steve.clone(), msg).unwrap_err(), NameServiceError::NotFound { name_id: expected_id + 1 } @@ -261,7 +395,7 @@ mod tests { // Remove as correct owner succeeds let msg = ExecuteMsg::delete_id(expected_id); - let res = execute(deps.as_mut(), mock_env(), info_owner, msg).unwrap(); + let res = execute(deps.as_mut(), mock_env(), info_steve, msg).unwrap(); assert_eq!( get_attribute(&res, "delete_id", "name_id"), expected_id.to_string() diff --git a/contracts/name-service/src/contract/execute.rs b/contracts/name-service/src/contract/execute.rs index a7396c1229..e41e57d7f4 100644 --- a/contracts/name-service/src/contract/execute.rs +++ b/contracts/name-service/src/contract/execute.rs @@ -1,15 +1,19 @@ use crate::{ constants::{MAX_NUMBER_OF_NAMES_FOR_ADDRESS, MAX_NUMBER_OF_NAMES_PER_OWNER}, - error::{NameServiceError, Result}, - state, + state, NameServiceError, Result, }; use cosmwasm_std::{Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Response, Uint128}; +use nym_contracts_common::{ + signing::{MessageSignature, Verifier}, + IdentityKey, +}; use nym_name_service_common::{ events::{ new_delete_id_event, new_delete_name_event, new_register_event, new_update_deposit_required_event, }, - Address, NameId, NymName, RegisteredName, + signing_types::construct_name_register_sign_payload, + Address, NameDetails, NameId, NymName, RegisteredName, }; use super::query; @@ -86,17 +90,54 @@ fn return_deposit(name_to_delete: &RegisteredName) -> BankMsg { } } +fn verify_register_signature( + deps: Deps<'_>, + sender: Addr, + deposit: Coin, + name: NameDetails, + signature: MessageSignature, +) -> Result<()> { + // recover the public key + let public_key = decode_ed25519_identity_key(&name.identity_key)?; + + // reconstruct the payload + let nonce = state::get_signing_nonce(deps.storage, sender.clone())?; + + let msg = construct_name_register_sign_payload(nonce, sender, deposit, name); + + if deps.api.verify_message(msg, signature, &public_key)? { + Ok(()) + } else { + Err(NameServiceError::InvalidEd25519Signature) + } +} + +fn decode_ed25519_identity_key(encoded: &IdentityKey) -> Result<[u8; 32]> { + let mut public_key = [0u8; 32]; + let used = bs58::decode(encoded) + .into(&mut public_key) + .map_err(|err| NameServiceError::MalformedEd25519IdentityKey(err.to_string()))?; + + if used != 32 { + return Err(NameServiceError::MalformedEd25519IdentityKey( + "Too few bytes provided for the public key".into(), + )); + } + + Ok(public_key) +} + /// Register a new name. It will be assigned a new name id. pub fn register( deps: DepsMut, env: Env, info: MessageInfo, - name: NymName, - address: Address, + name: NameDetails, + owner_signature: MessageSignature, ) -> Result { - ensure_name_not_exists(deps.as_ref(), &name)?; + ensure_name_not_exists(deps.as_ref(), &name.name)?; ensure_max_names_per_owner(deps.as_ref(), info.sender.clone())?; - ensure_max_names_per_address(deps.as_ref(), address.clone())?; + ensure_max_names_per_address(deps.as_ref(), name.address.clone())?; let deposit_required = state::deposit_required(deps.storage)?; let denom = deposit_required.denom.clone(); @@ -104,16 +145,29 @@ pub fn register( .map_err(|err| NameServiceError::DepositRequired { source: err })?; ensure_correct_deposit(will_deposit, deposit_required.amount)?; + let deposit = Coin::new(will_deposit.u128(), denom); + + verify_register_signature( + deps.as_ref(), + info.sender.clone(), + deposit.clone(), + name.clone(), + owner_signature, + )?; + + state::increment_signing_nonce(deps.storage, info.sender.clone())?; + + let id = state::next_name_id_counter(deps.storage)?; let new_name = RegisteredName { - address, + id, name, owner: info.sender, block_height: env.block.height, - deposit: Coin::new(will_deposit.u128(), denom), + deposit, }; - let name_id = state::names::save(deps.storage, &new_name)?; + state::names::save(deps.storage, &new_name)?; - Ok(Response::new().add_event(new_register_event(name_id, new_name))) + Ok(Response::new().add_event(new_register_event(new_name))) } /// Delete an exsisting name. @@ -127,23 +181,20 @@ pub fn delete_id(deps: DepsMut, info: MessageInfo, name_id: NameId) -> Result Result { let name_to_delete = query::query_name(deps.as_ref(), name)?; - ensure_sender_authorized(info, &name_to_delete.name)?; + ensure_sender_authorized(info, &name_to_delete)?; - state::names::remove_id(deps.storage, name_to_delete.name_id)?; - let return_deposit_msg = return_deposit(&name_to_delete.name); + state::names::remove_id(deps.storage, name_to_delete.id)?; + let return_deposit_msg = return_deposit(&name_to_delete); Ok(Response::new() .add_message(return_deposit_msg) - .add_event(new_delete_name_event( - name_to_delete.name_id, - name_to_delete.name, - ))) + .add_event(new_delete_name_event(name_to_delete))) } /// Update the deposit required to register new names diff --git a/contracts/name-service/src/contract/query.rs b/contracts/name-service/src/contract/query.rs index 0ba8355e96..29b7b4b057 100644 --- a/contracts/name-service/src/contract/query.rs +++ b/contracts/name-service/src/contract/query.rs @@ -1,18 +1,17 @@ use cosmwasm_std::Deps; -use nym_contracts_common::ContractBuildInformation; +use nym_contracts_common::{signing::Nonce, ContractBuildInformation}; use nym_name_service_common::{ response::{ConfigResponse, NamesListResponse, PagedNamesListResponse}, - Address, NameEntry, NameId, NymName, + Address, NameId, NymName, RegisteredName, }; use crate::{ - error::Result, state::{self, names::PagedLoad}, + Result, }; -pub fn query_id(deps: Deps, name_id: NameId) -> Result { - let name = state::names::load_id(deps.storage, name_id)?; - Ok(NameEntry { name_id, name }) +pub fn query_id(deps: Deps, name_id: NameId) -> Result { + state::names::load_id(deps.storage, name_id) } pub fn query_owner(deps: Deps, owner: String) -> Result { @@ -26,9 +25,8 @@ pub fn query_address(deps: Deps, address: Address) -> Result Ok(NamesListResponse::new(names)) } -pub fn query_name(deps: Deps, name: NymName) -> Result { - state::names::load_name_entry(deps.storage, &name) - .map(|(name_id, name)| NameEntry::new(name_id, name)) +pub fn query_name(deps: Deps, name: NymName) -> Result { + state::names::load_name(deps.storage, &name) } pub fn query_all_paged( @@ -44,6 +42,11 @@ pub fn query_all_paged( Ok(PagedNamesListResponse::new(names, limit, start_next_after)) } +pub fn query_current_signing_nonce(deps: Deps<'_>, address: String) -> Result { + let address = deps.api.addr_validate(&address)?; + state::get_signing_nonce(deps.storage, address) +} + pub fn query_config(deps: Deps) -> Result { let config = state::load_config(deps.storage)?; Ok(config.into()) diff --git a/contracts/name-service/src/integration_tests.rs b/contracts/name-service/src/integration_tests.rs deleted file mode 100644 index b7e68ffdc8..0000000000 --- a/contracts/name-service/src/integration_tests.rs +++ /dev/null @@ -1,445 +0,0 @@ -//! Integration tests using cw-multi-test. - -use cosmwasm_std::Addr; -use nym_name_service_common::{ - response::{ConfigResponse, PagedNamesListResponse}, - Address, NameEntry, NymName, RegisteredName, -}; - -use crate::{ - constants::NAME_DEFAULT_RETRIEVAL_LIMIT, - error::NameServiceError, - test_helpers::{fixture::name_entry, helpers::nyms, test_setup::TestSetup}, -}; - -#[test] -fn instantiate_contract() { - TestSetup::new(); -} - -#[test] -fn query_config() { - assert_eq!( - TestSetup::new().query_config(), - ConfigResponse { - deposit_required: nyms(100), - } - ); -} - -#[test] -fn register_and_query_name() { - let mut setup = TestSetup::new(); - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: None, - } - ); - - // Register a first name - let owner = Addr::unchecked("owner"); - let name = NymName::new("steves-server").unwrap(); - let nym_address = Address::new("nym-address"); - assert_eq!(setup.contract_balance(), nyms(0)); - assert_eq!(setup.balance(&owner), nyms(250)); - setup.register(name.clone(), nym_address.clone(), owner.clone()); - - // Deposit is deposited to contract and deducted from owners's balance - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!(setup.balance(&owner), nyms(150)); - - // We can query the full name list - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![NameEntry { - name_id: 1, - name: RegisteredName { - address: nym_address.clone(), - name: name.clone(), - owner: owner.clone(), - block_height: 12345, - deposit: nyms(100), - }, - }], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: Some(1), - } - ); - - // ... and we can query by id - assert_eq!( - setup.query_id(1), - NameEntry { - name_id: 1, - name: RegisteredName { - address: nym_address.clone(), - name: name.clone(), - owner: owner.clone(), - block_height: 12345, - deposit: nyms(100), - }, - } - ); - - // Register a second name - let owner2 = Addr::unchecked("owner2"); - let name2 = NymName::new("another_server").unwrap(); - let nym_address2 = Address::new("nymAddress2"); - setup.register(name2.clone(), nym_address2.clone(), owner2.clone()); - - assert_eq!(setup.contract_balance(), nyms(200)); - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![ - name_entry(1, name, nym_address, owner), - name_entry(2, name2, nym_address2, owner2) - ], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: Some(2), - } - ); -} - -#[test] -fn cant_register_a_name_without_funds() { - let mut setup = TestSetup::new(); - assert_eq!(setup.contract_balance(), nyms(0)); - assert_eq!(setup.balance("owner"), nyms(250)); - setup.register( - NymName::new("my_name").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!(setup.balance("owner"), nyms(150)); - setup.register( - NymName::new("my_name2").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - assert_eq!(setup.contract_balance(), nyms(200)); - assert_eq!(setup.balance("owner"), nyms(50)); - let res = setup - .try_register( - NymName::new("my_name3").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ) - .unwrap_err(); - assert_eq!( - res.downcast::().unwrap(), - cosmwasm_std::StdError::Overflow { - source: cosmwasm_std::OverflowError::new( - cosmwasm_std::OverflowOperation::Sub, - "50", - "100" - ) - } - ); -} - -#[test] -fn delete_name() { - let mut setup = TestSetup::new(); - setup.register( - NymName::new("my_name").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!(setup.balance("owner"), nyms(150)); - assert!(!setup.query_all().names.is_empty()); - setup.delete(1, Addr::unchecked("owner")); - - // Deleting the name returns the deposit to the owner - assert_eq!(setup.contract_balance(), nyms(0)); - assert_eq!(setup.balance("owner"), nyms(250)); - assert!(setup.query_all().names.is_empty()); -} - -#[test] -fn only_owner_can_delete_name() { - let mut setup = TestSetup::new(); - assert_eq!(setup.contract_balance(), nyms(0)); - setup.register( - NymName::new("name").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - assert_eq!(setup.contract_balance(), nyms(100)); - assert!(!setup.query_all().names.is_empty()); - - let delete_resp = setup - .try_delete(1, Addr::unchecked("not_owner")) - .unwrap_err(); - - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!( - delete_resp.downcast::().unwrap(), - NameServiceError::Unauthorized { - sender: Addr::unchecked("not_owner") - } - ); -} - -#[test] -fn cant_delete_name_that_does_not_exist() { - let mut setup = TestSetup::new(); - setup.register( - NymName::new("foo").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - assert_eq!(setup.contract_balance(), nyms(100)); - assert!(!setup.query_all().names.is_empty()); - - let delete_resp = setup.try_delete(0, Addr::unchecked("owner")).unwrap_err(); - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!( - delete_resp.downcast::().unwrap(), - NameServiceError::NotFound { name_id: 0 } - ); - - let delete_resp = setup.try_delete(2, Addr::unchecked("owner")).unwrap_err(); - assert_eq!(setup.contract_balance(), nyms(100)); - assert_eq!( - delete_resp.downcast::().unwrap(), - NameServiceError::NotFound { name_id: 2 } - ); - - assert!(!setup.query_all().names.is_empty()); - setup.delete(1, Addr::unchecked("owner")); - assert_eq!(setup.contract_balance(), nyms(0)); - assert!(setup.query_all().names.is_empty()); -} - -#[test] -fn cant_register_the_same_name_multiple_times() { - let mut setup = TestSetup::new(); - - setup.register( - NymName::new("name").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ); - let resp = setup - .try_register( - NymName::new("name").unwrap(), - Address::new("nymAddress"), - Addr::unchecked("owner"), - ) - .unwrap_err(); - - assert_eq!( - resp.downcast::().unwrap(), - NameServiceError::NameAlreadyRegistered { - name: NymName::new("name").unwrap() - } - ); -} - -#[test] -fn can_register_multiple_names_for_the_same_nym_address() { - let mut setup = TestSetup::new(); - let name1 = NymName::new("name1").unwrap(); - let name2 = NymName::new("name2").unwrap(); - let address = Address::new("nymaddress"); - let owner = Addr::unchecked("owner"); - - setup.register(name1.clone(), address.clone(), owner.clone()); - setup.register(name2.clone(), address.clone(), owner.clone()); - - assert_eq!( - setup.query_all().names, - vec![ - name_entry(1, name1, address.clone(), owner.clone()), - name_entry(2, name2, address, owner) - ], - ); -} - -#[test] -fn register_multiple_names_and_deleting_by_name() { - let mut setup = TestSetup::new(); - let owner1 = Addr::unchecked("wealthy_owner_1"); - let owner2 = Addr::unchecked("wealthy_owner_2"); - let nym_address1 = Address::new("nymaddress1"); - let nym_address2 = Address::new("nymaddress2"); - let name1 = NymName::new("name1").unwrap(); - let name2 = NymName::new("name2").unwrap(); - let name3 = NymName::new("name3").unwrap(); - let name4 = NymName::new("name4").unwrap(); - let name5 = NymName::new("name5").unwrap(); - - // We register the same address three times, but with different owners - assert_eq!(setup.contract_balance(), nyms(0)); - assert_eq!(setup.balance(&owner1), nyms(1000)); - setup.register(name1.clone(), nym_address1.clone(), owner1.clone()); - setup.register(name2.clone(), nym_address1.clone(), owner1.clone()); - setup.register(name3.clone(), nym_address2.clone(), owner1.clone()); - setup.register(name4.clone(), nym_address1.clone(), owner2.clone()); - setup.register(name5.clone(), nym_address2.clone(), owner2.clone()); - - assert_eq!(setup.contract_balance(), nyms(500)); - assert_eq!(setup.balance(&owner1), nyms(700)); - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![ - name_entry(1, name1.clone(), nym_address1.clone(), owner1.clone()), - name_entry(2, name2.clone(), nym_address1.clone(), owner1.clone()), - name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()), - name_entry(4, name4.clone(), nym_address1.clone(), owner2.clone()), - name_entry(5, name5.clone(), nym_address2.clone(), owner2.clone()), - ], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: Some(5), - } - ); - - setup.delete_name(name1, owner1.clone()); - - assert_eq!(setup.contract_balance(), nyms(400)); - assert_eq!(setup.balance(&owner1), nyms(800)); - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![ - name_entry(2, name2, nym_address1.clone(), owner1.clone()), - name_entry(3, name3, nym_address2.clone(), owner1), - name_entry(4, name4, nym_address1, owner2.clone()), - name_entry(5, name5, nym_address2, owner2), - ], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: Some(5), - } - ); -} - -#[test] -fn check_paging() { - let mut setup = TestSetup::new(); - let owner1 = Addr::unchecked("wealthy_owner_1"); - let owner2 = Addr::unchecked("wealthy_owner_2"); - let nym_address1 = Address::new("nymAddress1"); - let nym_address2 = Address::new("nymAddress2"); - let name1 = NymName::new("name1").unwrap(); - let name2 = NymName::new("name2").unwrap(); - let name3 = NymName::new("name3").unwrap(); - let name4 = NymName::new("name4").unwrap(); - let name5 = NymName::new("name5").unwrap(); - - // We register the same address three times, but with different owners - assert_eq!(setup.contract_balance(), nyms(0)); - assert_eq!(setup.balance(&owner1), nyms(1000)); - setup.register(name1.clone(), nym_address1.clone(), owner1.clone()); - setup.register(name2.clone(), nym_address1.clone(), owner1.clone()); - setup.register(name3.clone(), nym_address2.clone(), owner1.clone()); - setup.register(name4.clone(), nym_address1.clone(), owner2.clone()); - setup.register(name5.clone(), nym_address2.clone(), owner2.clone()); - - assert_eq!(setup.contract_balance(), nyms(500)); - assert_eq!(setup.balance(&owner1), nyms(700)); - assert_eq!( - setup.query_all(), - PagedNamesListResponse { - names: vec![ - name_entry(1, name1.clone(), nym_address1.clone(), owner1.clone()), - name_entry(2, name2.clone(), nym_address1.clone(), owner1.clone()), - name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()), - name_entry(4, name4.clone(), nym_address1.clone(), owner2.clone()), - name_entry(5, name5, nym_address2.clone(), owner2.clone()), - ], - per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, - start_next_after: Some(5), - } - ); - - setup.delete_name(name1, owner1.clone()); - - assert_eq!( - setup.query_all_with_limit(Some(2), None), - PagedNamesListResponse { - names: vec![ - name_entry(2, name2, nym_address1.clone(), owner1.clone()), - name_entry(3, name3.clone(), nym_address2.clone(), owner1.clone()), - ], - per_page: 2, - start_next_after: Some(3), - } - ); - - assert_eq!( - setup.query_all_with_limit(Some(2), Some(2)), - PagedNamesListResponse { - names: vec![ - name_entry(3, name3, nym_address2, owner1), - name_entry(4, name4, nym_address1, owner2), - ], - per_page: 2, - start_next_after: Some(4), - } - ); -} - -#[test] -fn name_id_is_not_resused_when_deleting_and_then_adding_a_new_names() { - let mut setup = TestSetup::new(); - setup.register( - NymName::new("myname1").unwrap(), - Address::new("nymAddress1"), - Addr::unchecked("owner1"), - ); - setup.register( - NymName::new("myname2").unwrap(), - Address::new("nymAddress2"), - Addr::unchecked("owner2"), - ); - setup.register( - NymName::new("myname3").unwrap(), - Address::new("nymAddress3"), - Addr::unchecked("owner3"), - ); - - setup.delete(1, Addr::unchecked("owner1")); - setup.delete(3, Addr::unchecked("owner3")); - - assert_eq!( - setup.query_all().names, - vec![name_entry( - 2, - NymName::new("myname2").unwrap(), - Address::new("nymAddress2"), - Addr::unchecked("owner2") - )] - ); - - setup.register( - NymName::new("myname4").unwrap(), - Address::new("nymAddress4"), - Addr::unchecked("owner4"), - ); - - assert_eq!( - setup.query_all().names, - vec![ - name_entry( - 2, - NymName::new("myname2").unwrap(), - Address::new("nymAddress2"), - Addr::unchecked("owner2") - ), - name_entry( - 4, - NymName::new("myname4").unwrap(), - Address::new("nymAddress4"), - Addr::unchecked("owner4") - ) - ] - ); -} diff --git a/contracts/name-service/src/integration_tests/delete.rs b/contracts/name-service/src/integration_tests/delete.rs new file mode 100644 index 0000000000..feede51739 --- /dev/null +++ b/contracts/name-service/src/integration_tests/delete.rs @@ -0,0 +1,148 @@ +use cosmwasm_std::Addr; +use nym_name_service_common::{response::PagedNamesListResponse, Address, NymName}; +use rstest::rstest; + +use crate::{ + constants::NAME_DEFAULT_RETRIEVAL_LIMIT, + test_helpers::{fixture::new_name, helpers::nyms}, + NameServiceError, +}; + +use super::test_setup::TestSetup; + +#[rstest::fixture] +fn setup() -> TestSetup { + TestSetup::new() +} + +#[rstest] +fn delete_name(mut setup: TestSetup) { + setup.sign_and_register( + &NymName::new("my_name").unwrap(), + &Address::new("address"), + &Addr::unchecked("owner"), + &nyms(100), + ); + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!(setup.balance("owner"), nyms(150)); + assert!(!setup.query_all().names.is_empty()); + setup.delete(1, Addr::unchecked("owner")); + + // Deleting the name returns the deposit to the owner + assert_eq!(setup.contract_balance(), nyms(0)); + assert_eq!(setup.balance("owner"), nyms(250)); + assert!(setup.query_all().names.is_empty()); +} + +#[rstest] +fn only_owner_can_delete_name(mut setup: TestSetup) { + assert_eq!(setup.contract_balance(), nyms(0)); + setup.sign_and_register( + &NymName::new("name").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + assert_eq!(setup.contract_balance(), nyms(100)); + assert!(!setup.query_all().names.is_empty()); + + let delete_resp = setup + .try_delete(1, Addr::unchecked("not_owner")) + .unwrap_err(); + + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!( + delete_resp.downcast::().unwrap(), + NameServiceError::Unauthorized { + sender: Addr::unchecked("not_owner") + } + ); +} + +#[rstest] +fn cant_delete_name_that_does_not_exist(mut setup: TestSetup) { + setup.sign_and_register( + &NymName::new("foo").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + assert_eq!(setup.contract_balance(), nyms(100)); + assert!(!setup.query_all().names.is_empty()); + + let delete_resp = setup.try_delete(0, Addr::unchecked("owner")).unwrap_err(); + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!( + delete_resp.downcast::().unwrap(), + NameServiceError::NotFound { name_id: 0 } + ); + + let delete_resp = setup.try_delete(2, Addr::unchecked("owner")).unwrap_err(); + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!( + delete_resp.downcast::().unwrap(), + NameServiceError::NotFound { name_id: 2 } + ); + + assert!(!setup.query_all().names.is_empty()); + setup.delete(1, Addr::unchecked("owner")); + assert_eq!(setup.contract_balance(), nyms(0)); + assert!(setup.query_all().names.is_empty()); +} + +#[rstest] +fn register_multiple_names_and_deleting_by_name(mut setup: TestSetup) { + let owner1 = Addr::unchecked("wealthy_owner_1"); + let owner2 = Addr::unchecked("wealthy_owner_2"); + let address1 = Address::new("address1"); + let address2 = Address::new("address2"); + let name1 = NymName::new("name1").unwrap(); + let name2 = NymName::new("name2").unwrap(); + let name3 = NymName::new("name3").unwrap(); + let name4 = NymName::new("name4").unwrap(); + let name5 = NymName::new("name5").unwrap(); + + // We register the same address three times, but with different owners + assert_eq!(setup.contract_balance(), nyms(0)); + assert_eq!(setup.balance(&owner1), nyms(1000)); + let s1 = setup.sign_and_register(&name1, &address1, &owner1, &nyms(100)); + let s2 = setup.sign_and_register(&name2, &address1, &owner1, &nyms(100)); + let s3 = setup.sign_and_register(&name3, &address2, &owner1, &nyms(100)); + let s4 = setup.sign_and_register(&name4, &address1, &owner2, &nyms(100)); + let s5 = setup.sign_and_register(&name5, &address2, &owner2, &nyms(100)); + + assert_eq!(setup.contract_balance(), nyms(500)); + assert_eq!(setup.balance(&owner1), nyms(700)); + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![ + new_name(1, &name1, &address1, &owner1, s1.identity_key()), + new_name(2, &name2, &address1, &owner1, s2.identity_key()), + new_name(3, &name3, &address2, &owner1, s3.identity_key()), + new_name(4, &name4, &address1, &owner2, s4.identity_key()), + new_name(5, &name5, &address2, &owner2, s5.identity_key()), + ], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: Some(5), + } + ); + + setup.delete_name(name1, owner1.clone()); + + assert_eq!(setup.contract_balance(), nyms(400)); + assert_eq!(setup.balance(&owner1), nyms(800)); + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![ + new_name(2, &name2, &address1, &owner1, s2.identity_key()), + new_name(3, &name3, &address2, &owner1, s3.identity_key()), + new_name(4, &name4, &address1, &owner2, s4.identity_key()), + new_name(5, &name5, &address2, &owner2, s5.identity_key()), + ], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: Some(5), + } + ); +} diff --git a/contracts/name-service/src/integration_tests/mod.rs b/contracts/name-service/src/integration_tests/mod.rs new file mode 100644 index 0000000000..28905d63c4 --- /dev/null +++ b/contracts/name-service/src/integration_tests/mod.rs @@ -0,0 +1,13 @@ +//! Integration tests using cw-multi-test. + +mod delete; +mod name_id; +mod query; +mod register; +mod test_name; +mod test_setup; + +#[test] +fn instantiate_contract() { + test_setup::TestSetup::new(); +} diff --git a/contracts/name-service/src/integration_tests/name_id.rs b/contracts/name-service/src/integration_tests/name_id.rs new file mode 100644 index 0000000000..39f5a993cf --- /dev/null +++ b/contracts/name-service/src/integration_tests/name_id.rs @@ -0,0 +1,70 @@ +use cosmwasm_std::Addr; +use nym_name_service_common::{Address, NymName}; + +use crate::test_helpers::{fixture::new_name, helpers::nyms}; + +use super::test_setup::TestSetup; + +#[test] +fn name_id_is_not_resused_when_deleting_and_then_adding_a_new_names() { + let mut setup = TestSetup::new(); + setup.sign_and_register( + &NymName::new("myname1").unwrap(), + &Address::new("nymAddress1"), + &Addr::unchecked("owner1"), + &nyms(100), + ); + let s2 = setup.sign_and_register( + &NymName::new("myname2").unwrap(), + &Address::new("nymAddress2"), + &Addr::unchecked("owner2"), + &nyms(100), + ); + setup.sign_and_register( + &NymName::new("myname3").unwrap(), + &Address::new("nymAddress3"), + &Addr::unchecked("owner3"), + &nyms(100), + ); + + setup.delete(1, Addr::unchecked("owner1")); + setup.delete(3, Addr::unchecked("owner3")); + + assert_eq!( + setup.query_all().names, + vec![new_name( + 2, + &NymName::new("myname2").unwrap(), + &Address::new("nymAddress2"), + &Addr::unchecked("owner2"), + s2.identity_key(), + )] + ); + + let s4 = setup.sign_and_register( + &NymName::new("myname4").unwrap(), + &Address::new("nymAddress4"), + &Addr::unchecked("owner4"), + &nyms(100), + ); + + assert_eq!( + setup.query_all().names, + vec![ + new_name( + 2, + &NymName::new("myname2").unwrap(), + &Address::new("nymAddress2"), + &Addr::unchecked("owner2"), + s2.identity_key(), + ), + new_name( + 4, + &NymName::new("myname4").unwrap(), + &Address::new("nymAddress4"), + &Addr::unchecked("owner4"), + s4.identity_key(), + ) + ] + ); +} diff --git a/contracts/name-service/src/integration_tests/query.rs b/contracts/name-service/src/integration_tests/query.rs new file mode 100644 index 0000000000..67069a0a1d --- /dev/null +++ b/contracts/name-service/src/integration_tests/query.rs @@ -0,0 +1,88 @@ +use cosmwasm_std::Addr; +use nym_name_service_common::{ + response::{ConfigResponse, PagedNamesListResponse}, + Address, NymName, +}; + +use crate::{ + constants::NAME_DEFAULT_RETRIEVAL_LIMIT, + test_helpers::{fixture::new_name, helpers::nyms}, +}; + +use super::test_setup::TestSetup; + +#[test] +fn query_config() { + assert_eq!( + TestSetup::new().query_config(), + ConfigResponse { + deposit_required: nyms(100), + } + ); +} + +#[test] +fn check_paging() { + let mut setup = TestSetup::new(); + let owner1 = Addr::unchecked("wealthy_owner_1"); + let owner2 = Addr::unchecked("wealthy_owner_2"); + let address1 = Address::new("nymAddress1"); + let address2 = Address::new("nymAddress2"); + let name1 = NymName::new("name1").unwrap(); + let name2 = NymName::new("name2").unwrap(); + let name3 = NymName::new("name3").unwrap(); + let name4 = NymName::new("name4").unwrap(); + let name5 = NymName::new("name5").unwrap(); + + // We register the same address three times, but with different owners + assert_eq!(setup.contract_balance(), nyms(0)); + assert_eq!(setup.balance(&owner1), nyms(1000)); + let s1 = setup.sign_and_register(&name1, &address1, &owner1, &nyms(100)); + let s2 = setup.sign_and_register(&name2, &address1, &owner1, &nyms(100)); + let s3 = setup.sign_and_register(&name3, &address2, &owner1, &nyms(100)); + let s4 = setup.sign_and_register(&name4, &address1, &owner2, &nyms(100)); + let s5 = setup.sign_and_register(&name5, &address2, &owner2, &nyms(100)); + + assert_eq!(setup.contract_balance(), nyms(500)); + assert_eq!(setup.balance(&owner1), nyms(700)); + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![ + new_name(1, &name1, &address1, &owner1, s1.identity_key()), + new_name(2, &name2, &address1, &owner1, s2.identity_key()), + new_name(3, &name3, &address2, &owner1, s3.identity_key()), + new_name(4, &name4, &address1, &owner2, s4.identity_key()), + new_name(5, &name5, &address2, &owner2, s5.identity_key()), + ], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: Some(5), + } + ); + + setup.delete_name(name1, owner1.clone()); + + assert_eq!( + setup.query_all_with_limit(Some(2), None), + PagedNamesListResponse { + names: vec![ + new_name(2, &name2, &address1, &owner1, s2.identity_key()), + new_name(3, &name3, &address2, &owner1, s3.identity_key()), + ], + per_page: 2, + start_next_after: Some(3), + } + ); + + assert_eq!( + setup.query_all_with_limit(Some(2), Some(2)), + PagedNamesListResponse { + names: vec![ + new_name(3, &name3, &address2, &owner1, s3.identity_key()), + new_name(4, &name4, &address1, &owner2, s4.identity_key()), + ], + per_page: 2, + start_next_after: Some(4), + } + ); +} diff --git a/contracts/name-service/src/integration_tests/register.rs b/contracts/name-service/src/integration_tests/register.rs new file mode 100644 index 0000000000..9d6d339dbc --- /dev/null +++ b/contracts/name-service/src/integration_tests/register.rs @@ -0,0 +1,269 @@ +use cosmwasm_std::Addr; +use nym_name_service_common::{ + error::NameServiceError, response::PagedNamesListResponse, Address, NameDetails, NymName, + RegisteredName, +}; +use rstest::rstest; + +use crate::{ + constants::NAME_DEFAULT_RETRIEVAL_LIMIT, + test_helpers::{fixture::new_name, helpers::nyms}, +}; + +use super::test_setup::TestSetup; + +#[rstest::fixture] +fn setup() -> TestSetup { + TestSetup::new() +} + +#[rstest] +fn basic_register(mut setup: TestSetup) { + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: None, + } + ); + + // Register a first name + let owner = Addr::unchecked("owner"); + let name = NymName::new("steves-server").unwrap(); + let nym_address = Address::new("nym-address"); + assert_eq!(setup.contract_balance(), nyms(0)); + assert_eq!(setup.balance(&owner), nyms(250)); + assert_eq!(setup.query_signing_nonce(owner.to_string()), 0); + + let reg_name = setup.new_name(&name, &nym_address); + let payload = setup.payload_to_sign(&owner, &nyms(100), ®_name.name); + let reg_name = reg_name.sign(payload); + setup.register(®_name, &owner); + + // Deposit is deposited to contract and deducted from owners's balance + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!(setup.balance(&owner), nyms(150)); + + // The signing nonce has been incremented + assert_eq!(setup.query_signing_nonce(owner.to_string()), 1); + + // We can query the full name list + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![RegisteredName { + id: 1, + name: NameDetails { + name: name.clone(), + address: nym_address.clone(), + identity_key: reg_name.identity_key().to_string(), + }, + owner: owner.clone(), + block_height: 12345, + deposit: nyms(100), + }], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: Some(1), + } + ); + + // ... and we can query by id + assert_eq!( + setup.query_id(1), + RegisteredName { + id: 1, + name: reg_name.details().clone(), + owner: owner.clone(), + block_height: 12345, + deposit: nyms(100), + } + ); + + // Register a second name + let owner2 = Addr::unchecked("owner2"); + let name2 = NymName::new("another_server").unwrap(); + let nym_address2 = Address::new("nymAddress2"); + let reg_name2 = setup.new_signed_name(&name2, &nym_address2, &owner2, &nyms(100)); + setup.register(®_name2, &owner2); + + assert_eq!(setup.contract_balance(), nyms(200)); + assert_eq!( + setup.query_all(), + PagedNamesListResponse { + names: vec![ + new_name(1, &name, &nym_address, &owner, reg_name.identity_key()), + new_name(2, &name2, &nym_address2, &owner2, reg_name2.identity_key()), + ], + per_page: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, + start_next_after: Some(2), + } + ); +} + +#[rstest] +fn register_fails_when_owner_mismatch(mut setup: TestSetup) { + let owner = Addr::unchecked("owner"); + let name = NymName::new("steves-server").unwrap(); + let nym_address = Address::new("nym-address"); + let reg_name = setup.new_signed_name(&name, &nym_address, &owner, &nyms(100)); + let res = setup + .try_register(®_name, &Addr::unchecked("owner2")) + .unwrap_err(); + assert_eq!( + res.downcast::().unwrap(), + NameServiceError::InvalidEd25519Signature + ); +} + +#[rstest] +fn signing_nonce_is_increased_when_registering(mut setup: TestSetup) { + let owner1 = Addr::unchecked("owner1"); + let owner2 = Addr::unchecked("owner2"); + + assert_eq!(setup.query_signing_nonce(owner1.to_string()), 0); + assert_eq!(setup.query_signing_nonce(owner2.to_string()), 0); + + setup.sign_and_register( + &NymName::new("myname1").unwrap(), + &Address::new("address1"), + &owner1, + &nyms(100), + ); + + assert_eq!(setup.query_signing_nonce(owner1.to_string()), 1); + assert_eq!(setup.query_signing_nonce(owner2.to_string()), 0); + + setup.sign_and_register( + &NymName::new("myname2").unwrap(), + &Address::new("address2"), + &owner2, + &nyms(100), + ); + + assert_eq!(setup.query_signing_nonce(owner1.to_string()), 1); + assert_eq!(setup.query_signing_nonce(owner2.to_string()), 1); + + setup.sign_and_register( + &NymName::new("myname3").unwrap(), + &Address::new("address3"), + &owner2, + &nyms(100), + ); + + assert_eq!(setup.query_signing_nonce(owner1.to_string()), 1); + assert_eq!(setup.query_signing_nonce(owner2.to_string()), 2); +} + +#[rstest] +fn creating_two_names_in_a_row_without_announcing_fails(mut setup: TestSetup) { + let owner = Addr::unchecked("wealthy_owner_1"); + let name1 = NymName::new("steves-server1").unwrap(); + let name2 = NymName::new("steves-server2").unwrap(); + let address1 = Address::new("nymAddress1"); + let address2 = Address::new("nymAddress2"); + let deposit = nyms(100); + + let s1 = setup.new_signed_name(&name1, &address1, &owner, &deposit); + + // This second name will be signed with the same nonce + let s2 = setup.new_signed_name(&name2, &address2, &owner, &deposit); + + // Announce the first service works, and this increments the nonce + setup.register(&s1, &owner); + + // Now the nonce has been incremented, and the signature will not match + let resp: NameServiceError = setup + .try_register(&s2, &owner) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(resp, NameServiceError::InvalidEd25519Signature,); +} + +#[rstest] +fn cant_register_a_name_without_funds(mut setup: TestSetup) { + assert_eq!(setup.contract_balance(), nyms(0)); + assert_eq!(setup.balance("owner"), nyms(250)); + let name1 = setup.new_signed_name( + &NymName::new("my_name").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + setup.register(&name1, &Addr::unchecked("owner")); + assert_eq!(setup.contract_balance(), nyms(100)); + assert_eq!(setup.balance("owner"), nyms(150)); + + let name2 = setup.new_signed_name( + &NymName::new("my_name2").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + setup.register(&name2, &Addr::unchecked("owner")); + assert_eq!(setup.contract_balance(), nyms(200)); + assert_eq!(setup.balance("owner"), nyms(50)); + let name3 = setup.new_signed_name( + &NymName::new("my_name3").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + let res = setup + .try_register(&name3, &Addr::unchecked("owner")) + .unwrap_err(); + assert_eq!( + res.downcast::().unwrap(), + cosmwasm_std::StdError::Overflow { + source: cosmwasm_std::OverflowError::new( + cosmwasm_std::OverflowOperation::Sub, + "50", + "100" + ) + } + ); +} + +#[rstest] +fn cant_register_the_same_name_multiple_times(mut setup: TestSetup) { + let name1 = setup.new_signed_name( + &NymName::new("name").unwrap(), + &Address::new("nymAddress"), + &Addr::unchecked("owner"), + &nyms(100), + ); + setup.register(&name1, &Addr::unchecked("owner")); + let resp = setup + .try_register(&name1, &Addr::unchecked("owner")) + .unwrap_err(); + + assert_eq!( + resp.downcast::().unwrap(), + NameServiceError::NameAlreadyRegistered { + name: NymName::new("name").unwrap() + } + ); +} + +#[rstest] +fn can_register_multiple_names_for_the_same_nym_address(mut setup: TestSetup) { + let name1 = NymName::new("name1").unwrap(); + let name2 = NymName::new("name2").unwrap(); + let address = Address::new("nymaddress"); + let owner = Addr::unchecked("owner"); + + let reg_name1 = setup.new_signed_name(&name1, &address, &owner, &nyms(100)); + setup.register(®_name1, &owner); + let reg_name2 = setup.new_signed_name(&name2, &address, &owner, &nyms(100)); + setup.register(®_name2, &owner); + + assert_eq!( + setup.query_all().names, + vec![ + new_name(1, &name1, &address, &owner, reg_name1.identity_key()), + new_name(2, &name2, &address, &owner, reg_name2.identity_key()), + ], + ); +} +// diff --git a/contracts/name-service/src/integration_tests/test_name.rs b/contracts/name-service/src/integration_tests/test_name.rs new file mode 100644 index 0000000000..f33233a0a0 --- /dev/null +++ b/contracts/name-service/src/integration_tests/test_name.rs @@ -0,0 +1,75 @@ +use nym_contracts_common::{signing::MessageSignature, IdentityKey}; +use nym_crypto::asymmetric::identity; +use nym_name_service_common::{ + signing_types::SignableNameRegisterMsg, Address, NameDetails, NymName, +}; +use rand_chacha::ChaCha20Rng; + +use crate::test_helpers::signing::ed25519_sign_message; + +pub struct TestName { + pub name: NameDetails, + pub keys: identity::KeyPair, + pub rng: ChaCha20Rng, +} + +impl TestName { + pub fn new(rng: &mut ChaCha20Rng, name: NymName, address: Address) -> Self { + let keys = identity::KeyPair::new(rng); + let name = NameDetails { + name, + address, + identity_key: keys.public_key().to_base58_string(), + }; + Self { + name, + keys, + rng: rng.clone(), + } + } + + pub fn identity_key(&self) -> &IdentityKey { + &self.name.identity_key + } + + pub fn details(&self) -> &NameDetails { + &self.name + } + + pub fn sign(self, payload: SignableNameRegisterMsg) -> SignedTestName { + let owner_signature = ed25519_sign_message(payload, self.keys.private_key()); + SignedTestName { + name: self.name, + keys: self.keys, + owner_signature, + } + } +} + +impl From for NameDetails { + fn from(test_name: TestName) -> Self { + test_name.name + } +} + +pub struct SignedTestName { + pub name: NameDetails, + pub keys: identity::KeyPair, + pub owner_signature: MessageSignature, +} + +impl SignedTestName { + pub fn identity_key(&self) -> &IdentityKey { + &self.name.identity_key + } + + pub fn details(&self) -> &NameDetails { + &self.name + } +} + +impl From for NameDetails { + fn from(signed_name: SignedTestName) -> Self { + signed_name.name + } +} diff --git a/contracts/name-service/src/test_helpers/test_setup.rs b/contracts/name-service/src/integration_tests/test_setup.rs similarity index 64% rename from contracts/name-service/src/test_helpers/test_setup.rs rename to contracts/name-service/src/integration_tests/test_setup.rs index 98afbb60a0..264d2d236b 100644 --- a/contracts/name-service/src/test_helpers/test_setup.rs +++ b/contracts/name-service/src/integration_tests/test_setup.rs @@ -1,13 +1,18 @@ use cosmwasm_std::{coins, Addr, Coin, Uint128}; use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor}; +use nym_contracts_common::signing::Nonce; use nym_name_service_common::{ msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, response::{ConfigResponse, PagedNamesListResponse}, - Address, NameEntry, NameId, NymName, + signing_types::{construct_name_register_sign_payload, SignableNameRegisterMsg}, + Address, NameDetails, NameId, NymName, RegisteredName, }; +use rand_chacha::ChaCha20Rng; use serde::de::DeserializeOwned; -use crate::test_helpers::helpers::get_app_attribute; +use crate::test_helpers::helpers::{get_app_attribute, test_rng}; + +use super::test_name::{SignedTestName, TestName}; const DENOM: &str = "unym"; const ADDRESSES: &[&str] = &[ @@ -19,6 +24,7 @@ const WEALTHY_ADDRESSES: &[&str] = &["wealthy_owner_1", "wealthy_owner_2"]; pub struct TestSetup { app: App, addr: Addr, + rng: ChaCha20Rng, } impl Default for TestSetup { @@ -44,7 +50,8 @@ impl TestSetup { let code = ContractWrapper::new(crate::execute, crate::instantiate, crate::query); let code_id = app.store_code(Box::new(code)); let addr = Self::instantiate(&mut app, code_id); - TestSetup { app, addr } + let rng = test_rng(); + TestSetup { app, addr, rng } } fn instantiate(app: &mut App, code_id: u64) -> Addr { @@ -76,7 +83,7 @@ impl TestSetup { self.query(&QueryMsg::Config {}) } - pub fn query_id(&self, name_id: NameId) -> NameEntry { + pub fn query_id(&self, name_id: NameId) -> RegisteredName { self.query(&QueryMsg::NameId { name_id }) } @@ -92,16 +99,48 @@ impl TestSetup { self.query(&QueryMsg::All { limit, start_after }) } + pub fn query_signing_nonce(&self, address: String) -> Nonce { + self.query(&QueryMsg::SigningNonce { address }) + } + + pub fn new_name(&mut self, name: &NymName, address: &Address) -> TestName { + TestName::new(&mut self.rng, name.clone(), address.clone()) + } + + pub fn payload_to_sign( + &mut self, + owner: &Addr, + deposit: &Coin, + name: &NameDetails, + ) -> SignableNameRegisterMsg { + let nonce = self.query_signing_nonce(owner.to_string()); + construct_name_register_sign_payload(nonce, owner.clone(), deposit.clone(), name.clone()) + } + + pub fn new_signed_name( + &mut self, + name: &NymName, + address: &Address, + owner: &Addr, + deposit: &Coin, + ) -> SignedTestName { + let name = self.new_name(name, address); + let payload = self.payload_to_sign(owner, deposit, name.details()); + name.sign(payload) + } + pub fn try_register( &mut self, - name: NymName, - address: Address, - owner: Addr, + name: &SignedTestName, + owner: &Addr, ) -> anyhow::Result { self.app.execute_contract( - owner, + owner.clone(), self.addr.clone(), - &ExecuteMsg::Register { name, address }, + &ExecuteMsg::Register { + name: name.name.clone(), + owner_signature: name.owner_signature.clone(), + }, &[Coin { denom: DENOM.to_string(), amount: Uint128::new(100), @@ -109,8 +148,8 @@ impl TestSetup { ) } - pub fn register(&mut self, name: NymName, address: Address, owner: Addr) -> AppResponse { - let resp = self.try_register(name, address, owner).unwrap(); + pub fn register(&mut self, name: &SignedTestName, owner: &Addr) -> AppResponse { + let resp = self.try_register(name, owner).unwrap(); assert_eq!( get_app_attribute(&resp, "wasm-register", "action"), "register" @@ -118,6 +157,19 @@ impl TestSetup { resp } + // Convenience function for creating a new signed name, and regsitering it + pub fn sign_and_register( + &mut self, + name: &NymName, + address: &Address, + owner: &Addr, + deposit: &Coin, + ) -> SignedTestName { + let signed_name = self.new_signed_name(name, address, owner, deposit); + self.register(&signed_name, owner); + signed_name + } + pub fn try_delete(&mut self, name_id: NameId, owner: Addr) -> anyhow::Result { self.app.execute_contract( owner, diff --git a/contracts/name-service/src/lib.rs b/contracts/name-service/src/lib.rs index c2d5b6c5d6..0f426dd72f 100644 --- a/contracts/name-service/src/lib.rs +++ b/contracts/name-service/src/lib.rs @@ -3,16 +3,15 @@ #![warn(clippy::expect_used)] #![warn(clippy::unwrap_used)] -use crate::error::Result; +pub use nym_name_service_common::error::{NameServiceError, Result}; + use nym_name_service_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response}; -use error::NameServiceError; mod contract; -mod error; mod state; pub mod constants; diff --git a/contracts/name-service/src/state/admin.rs b/contracts/name-service/src/state/admin.rs index e0ec915951..026e353626 100644 --- a/contracts/name-service/src/state/admin.rs +++ b/contracts/name-service/src/state/admin.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Addr, Deps, DepsMut}; use cw_controllers::Admin; -use crate::{constants::ADMIN_KEY, error::Result}; +use crate::{constants::ADMIN_KEY, Result}; const ADMIN: Admin = Admin::new(ADMIN_KEY); diff --git a/contracts/name-service/src/state/config.rs b/contracts/name-service/src/state/config.rs index ed032e670c..aa27617492 100644 --- a/contracts/name-service/src/state/config.rs +++ b/contracts/name-service/src/state/config.rs @@ -3,7 +3,7 @@ use cw_storage_plus::Item; use nym_name_service_common::response::ConfigResponse; use serde::{Deserialize, Serialize}; -use crate::{constants::CONFIG_KEY, error::Result}; +use crate::{constants::CONFIG_KEY, Result}; const CONFIG: Item = Item::new(CONFIG_KEY); diff --git a/contracts/name-service/src/state/mod.rs b/contracts/name-service/src/state/mod.rs index 4120ab91e8..8d86c67201 100644 --- a/contracts/name-service/src/state/mod.rs +++ b/contracts/name-service/src/state/mod.rs @@ -2,7 +2,9 @@ pub mod admin; pub mod config; pub mod name_id_counter; pub mod names; +pub mod nonce; pub(crate) use admin::{assert_admin, set_admin}; pub(crate) use config::{deposit_required, load_config, save_config, Config}; pub(crate) use name_id_counter::next_name_id_counter; +pub(crate) use nonce::{get_signing_nonce, increment_signing_nonce}; diff --git a/contracts/name-service/src/state/name_id_counter.rs b/contracts/name-service/src/state/name_id_counter.rs index bed384e7bb..c45e5169e6 100644 --- a/contracts/name-service/src/state/name_id_counter.rs +++ b/contracts/name-service/src/state/name_id_counter.rs @@ -2,7 +2,7 @@ use cosmwasm_std::Storage; use cw_storage_plus::Item; use nym_name_service_common::NameId; -use crate::{constants::NAME_ID_COUNTER_KEY, error::Result}; +use crate::{constants::NAME_ID_COUNTER_KEY, Result}; const NAME_ID_COUNTER: Item = Item::new(NAME_ID_COUNTER_KEY); @@ -16,33 +16,56 @@ pub(crate) fn next_name_id_counter(store: &mut dyn Storage) -> Result { #[cfg(test)] mod tests { - - use nym_name_service_common::NameEntry; + use cosmwasm_std::Addr; + use nym_name_service_common::RegisteredName; use crate::test_helpers::{ assert::assert_names, - fixture::name_fixture_name, - helpers::{delete_name_id, instantiate_test_contract, register_name}, + helpers::{nyms, test_rng}, + transactions::{delete_name_id, instantiate_test_contract, register_name}, }; #[test] fn get_next_name_id() { let mut deps = instantiate_test_contract(); + let mut rng = test_rng(); - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("foo")), 1); - assert_names( - deps.as_ref(), - &[NameEntry::new(1, name_fixture_name("foo"))], - ); - - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("bar")), 2); - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("baz")), 3); + let (id1, name1) = register_name(deps.as_mut(), &mut rng, "foo", "addr1", "steve"); + let (id2, name2) = register_name(deps.as_mut(), &mut rng, "bar", "addr2", "steve"); + let (id3, name3) = register_name(deps.as_mut(), &mut rng, "baz", "addr3", "steve"); + assert_eq!(id1, 1); + assert_eq!(id2, 2); + assert_eq!(id3, 3); + assert_eq!(name1.name.as_str(), "foo"); + assert_eq!(name1.address.as_str(), "addr1"); + assert_eq!(name2.name.as_str(), "bar"); + assert_eq!(name2.address.as_str(), "addr2"); + assert_eq!(name3.name.as_str(), "baz"); + assert_eq!(name3.address.as_str(), "addr3"); assert_names( deps.as_ref(), &[ - NameEntry::new(1, name_fixture_name("foo")), - NameEntry::new(2, name_fixture_name("bar")), - NameEntry::new(3, name_fixture_name("baz")), + RegisteredName { + id: 1, + name: name1, + owner: Addr::unchecked("steve"), + block_height: 12345, + deposit: nyms(100), + }, + RegisteredName { + id: 2, + name: name2, + owner: Addr::unchecked("steve"), + block_height: 12345, + deposit: nyms(100), + }, + RegisteredName { + id: 3, + name: name3, + owner: Addr::unchecked("steve"), + block_height: 12345, + deposit: nyms(100), + }, ], ); } @@ -50,34 +73,28 @@ mod tests { #[test] fn deleted_name_id_is_not_reused() { let mut deps = instantiate_test_contract(); + let mut rng = test_rng(); // Register two names - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("one")), 1); - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("two")), 2); - assert_names( - deps.as_ref(), - &[ - NameEntry::new(1, name_fixture_name("one")), - NameEntry::new(2, name_fixture_name("two")), - ], - ); + let (_, name1) = register_name(deps.as_mut(), &mut rng, "one", "sdfjkhsdfhr", "steve"); + register_name(deps.as_mut(), &mut rng, "two", "suereljer", "steve"); // Delete the last entry delete_name_id(deps.as_mut(), 2, "steve"); assert_names( deps.as_ref(), - &[NameEntry::new(1, name_fixture_name("one"))], + &[RegisteredName { + id: 1, + name: name1, + owner: Addr::unchecked("steve"), + block_height: 12345, + deposit: nyms(100), + }], ); // Create a third entry. The index should not reuse the previous entry that we just // deleted. - assert_eq!(register_name(deps.as_mut(), &name_fixture_name("two")), 3); - assert_names( - deps.as_ref(), - &[ - NameEntry::new(1, name_fixture_name("one")), - NameEntry::new(3, name_fixture_name("two")), - ], - ); + let (id3, _) = register_name(deps.as_mut(), &mut rng, "three", "ufd", "steve"); + assert_eq!(id3, 3); } } diff --git a/contracts/name-service/src/state/names.rs b/contracts/name-service/src/state/names.rs index de5203058a..3b2b02f82d 100644 --- a/contracts/name-service/src/state/names.rs +++ b/contracts/name-service/src/state/names.rs @@ -8,7 +8,7 @@ use crate::{ NAMES_ADDRESS_IDX_NAMESPACE, NAMES_NAME_IDX_NAMESPACE, NAMES_OWNER_IDX_NAMESPACE, NAMES_PK_NAMESPACE, NAME_DEFAULT_RETRIEVAL_LIMIT, NAME_MAX_RETRIEVAL_LIMIT, }, - error::{NameServiceError, Result}, + NameServiceError, Result, }; struct NameIndex<'a> { @@ -28,9 +28,9 @@ impl<'a> IndexList for NameIndex<'a> { fn names<'a>() -> IndexedMap<'a, NameId, RegisteredName, NameIndex<'a>> { let indexes = NameIndex { - name: UniqueIndex::new(|d| d.name.to_string(), NAMES_NAME_IDX_NAMESPACE), + name: UniqueIndex::new(|d| d.name.name.to_string(), NAMES_NAME_IDX_NAMESPACE), address: MultiIndex::new( - |_pk, d| d.address.to_string(), + |_pk, d| d.name.address.to_string(), NAMES_PK_NAMESPACE, NAMES_ADDRESS_IDX_NAMESPACE, ), @@ -43,19 +43,29 @@ fn names<'a>() -> IndexedMap<'a, NameId, RegisteredName, NameIndex<'a>> { IndexedMap::new(NAMES_PK_NAMESPACE, indexes) } -pub fn save(store: &mut dyn Storage, new_name: &RegisteredName) -> Result { - let name_id = super::next_name_id_counter(store)?; +pub fn save(store: &mut dyn Storage, new_name: &RegisteredName) -> Result<()> { + let name_id = new_name.id; names().save(store, name_id, new_name)?; - Ok(name_id) + Ok(()) } #[cfg(test)] -pub fn save_all(state: &mut dyn Storage, names: &[RegisteredName]) -> Result> { - let mut ids = vec![]; +pub fn save_all(state: &mut dyn Storage, names: &[RegisteredName]) -> Result<()> { for name in names { - ids.push(save(state, name)?); + save(state, name)?; } - Ok(ids) + Ok(()) +} + +pub fn remove_id(store: &mut dyn Storage, name_id: NameId) -> Result<()> { + Ok(names().remove(store, name_id)?) +} + +#[cfg(test)] +pub fn remove_name(store: &mut dyn Storage, name: NymName) -> Result { + let registered_name = load_name(store, &name)?; + remove_id(store, registered_name.id)?; + Ok(registered_name.id) } pub fn has_name_id(store: &dyn Storage, name_id: NameId) -> bool { @@ -66,23 +76,6 @@ pub fn has_name(store: &dyn Storage, name: &NymName) -> bool { load_name(store, name).is_ok() } -// Get the (key, name) entry for a given name -pub fn load_name_entry(store: &dyn Storage, name: &NymName) -> Result<(NameId, RegisteredName)> { - names() - .idx - .name - .range(store, None, None, Order::Ascending) - .find(|entry| { - if let Ok(entry) = entry { - &entry.1.name == name - } else { - false - } - }) - .ok_or(NameServiceError::NameNotFound { name: name.clone() })? - .map_err(NameServiceError::from) -} - pub fn load_id(store: &dyn Storage, name_id: NameId) -> Result { names().load(store, name_id).map_err(|err| match err { StdError::NotFound { .. } => NameServiceError::NotFound { name_id }, @@ -99,45 +92,33 @@ pub fn load_name(store: &dyn Storage, name: &NymName) -> Result .ok_or(NameServiceError::NameNotFound { name: name.clone() }) } -pub fn load_address( - store: &dyn Storage, - address: &Address, -) -> Result> { +pub fn load_address(store: &dyn Storage, address: &Address) -> Result> { names() .idx .address .prefix(address.to_string()) .range(store, None, None, Order::Ascending) .take(MAX_NUMBER_OF_NAMES_FOR_ADDRESS as usize) + .map(|res| res.map(|(_, name)| name)) .collect::>>() .map_err(NameServiceError::from) } -pub fn load_owner(store: &dyn Storage, owner: Addr) -> Result> { +pub fn load_owner(store: &dyn Storage, owner: Addr) -> Result> { names() .idx .owner .prefix(owner) .range(store, None, None, Order::Ascending) .take(MAX_NUMBER_OF_NAMES_PER_OWNER as usize) + .map(|res| res.map(|(_, name)| name)) .collect::>>() .map_err(NameServiceError::from) } -pub fn remove_id(store: &mut dyn Storage, name_id: NameId) -> Result<()> { - Ok(names().remove(store, name_id)?) -} - -#[cfg(test)] -pub fn remove_name(store: &mut dyn Storage, name: NymName) -> Result { - let name_info = load_name_entry(store, &name)?; - remove_id(store, name_info.0)?; - Ok(name_info.0) -} - #[derive(Debug, PartialEq)] pub struct PagedLoad { - pub names: Vec<(NameId, RegisteredName)>, + pub names: Vec, pub limit: usize, pub start_next_after: Option, } @@ -156,9 +137,10 @@ pub fn load_all_paged( let names = names() .range(store, start, None, Order::Ascending) .take(limit) + .map(|res| res.map(|(_, name)| name)) .collect::>>()?; - let start_next_after = names.last().map(|name| name.0); + let start_next_after = names.last().map(|name| name.id); Ok(PagedLoad { names, @@ -169,8 +151,6 @@ pub fn load_all_paged( #[cfg(test)] mod tests { - use std::iter::zip; - use cosmwasm_std::{ testing::{MockApi, MockQuerier}, MemoryStorage, OwnedDeps, @@ -178,11 +158,11 @@ mod tests { use rstest::rstest; use crate::{ - error::NameServiceError, test_helpers::{ fixture::{name_fixture, name_fixture_full}, - helpers::instantiate_test_contract, + transactions::instantiate_test_contract, }, + NameServiceError, }; use super::*; @@ -197,64 +177,55 @@ mod tests { #[rstest::fixture] fn uniq_names() -> Vec { vec![ - name_fixture_full("one", "address_one", "owner_one"), - name_fixture_full("two", "address_two", "owner_two"), - name_fixture_full("three", "address_three", "owner_three"), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_three", "owner_three"), ] } #[rstest::fixture] fn overlapping_addresses() -> Vec { vec![ - name_fixture_full("one", "address_one", "owner_one"), - name_fixture_full("two", "address_two", "owner_two"), - name_fixture_full("three", "address_two", "owner_three"), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_two", "owner_three"), ] } #[rstest::fixture] fn overlapping_owners() -> Vec { vec![ - name_fixture_full("one", "address_one", "owner_one"), - name_fixture_full("two", "address_two", "owner_two"), - name_fixture_full("three", "address_three", "owner_two"), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_three", "owner_two"), ] } - fn assert_not_registered(store: &dyn Storage, names: Vec, ids: Vec) { - let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect(); + fn assert_not_registered(store: &dyn Storage, names: Vec) { let loaded = load_all_paged(store, None, None).unwrap(); - for (id, name) in &names { - assert!(!has_name_id(store, *id)); - assert!(!has_name(store, &name.name)); - assert!(!loaded.names.iter().any(|(i, _)| i == id)); - assert!(!loaded.names.iter().any(|(_, n)| n == name)); + for name in &names { + assert!(!has_name_id(store, name.id)); + assert!(!has_name(store, name.entry())); + assert!(!loaded.names.iter().any(|l_name| l_name.id == name.id)); + assert!(!loaded.names.iter().any(|l_name| l_name == name)); } } - fn assert_registered(store: &dyn Storage, names: Vec, ids: Vec) { - assert!(names.len() == ids.len()); - let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect(); + fn assert_registered(store: &dyn Storage, names: Vec) { let loaded = load_all_paged(store, None, None).unwrap(); - for (id, name) in &names { - assert!(has_name_id(store, *id)); - assert!(has_name(store, &name.name)); - assert!(loaded.names.iter().filter(|(i, _)| i == id).count() == 1); - assert!(loaded.names.iter().filter(|(_, n)| n == name).count() == 1); + for name in &names { + assert!(has_name_id(store, name.id)); + assert!(has_name(store, name.entry())); + assert!(loaded.names.iter().filter(|n| n == &name).count() == 1); } } - fn assert_only_these_registered( - store: &dyn Storage, - names: Vec, - ids: Vec, - ) { - let last_id = *ids.last().unwrap(); - let names: Vec<(NameId, RegisteredName)> = zip(ids, names).collect(); - for (id, name) in &names { - assert!(has_name_id(store, *id)); - assert!(has_name(store, &name.name)); + fn assert_only_these_registered(store: &dyn Storage, names: Vec) { + for name in &names { + assert!(has_name_id(store, name.id)); + assert!(has_name(store, name.entry())); } + let last_id = names.last().unwrap().id; assert_eq!( load_all_paged(store, None, None).unwrap(), PagedLoad { @@ -267,102 +238,107 @@ mod tests { #[rstest] fn single_basic_save_works(mut deps: TestDeps) { - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); } #[rstest] fn save_same_name_twice_fails(mut deps: TestDeps) { - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert!(matches!( - save(deps.as_mut().storage, &name_fixture()).unwrap_err(), + save(deps.as_mut().storage, &name_fixture(2)).unwrap_err(), NameServiceError::Std(StdError::GenericErr { .. }) )); } + #[rstest] + fn remove_id_works(mut deps: TestDeps) { + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); + assert!(has_name_id(&deps.storage, 1)); + assert!(has_name(&deps.storage, name_fixture(1).entry())); + remove_id(deps.as_mut().storage, 1).unwrap(); + assert!(!has_name_id(&deps.storage, 1)); + assert!(!has_name(&deps.storage, name_fixture(1).entry())); + } + + #[rstest] + fn remove_name_works(mut deps: TestDeps) { + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); + assert!(has_name_id(&deps.storage, 1)); + assert!(has_name(&deps.storage, name_fixture(1).entry())); + remove_name(deps.as_mut().storage, name_fixture(1).name.name).unwrap(); + assert!(!has_name_id(&deps.storage, 1)); + assert!(!has_name(&deps.storage, name_fixture(1).entry())); + } + #[rstest] fn has_name_works(mut deps: TestDeps) { - assert!(!has_name(&deps.storage, &name_fixture().name)); - save(deps.as_mut().storage, &name_fixture()).unwrap(); - assert!(has_name(&deps.storage, &name_fixture().name)); + assert!(!has_name(&deps.storage, name_fixture(1).entry())); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); + assert!(has_name(&deps.storage, name_fixture(1).entry())); } #[rstest] fn has_name_id_works(mut deps: TestDeps) { assert!(!has_name_id(&deps.storage, 1)); - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert!(has_name_id(&deps.storage, 1)); } #[rstest] fn has_name_id_with_incorrect_id_fails(mut deps: TestDeps) { assert!(!has_name_id(&deps.storage, 2)); - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert!(!has_name_id(&deps.storage, 2)); } - #[rstest] - fn load_name_entry_works(mut deps: TestDeps) { - assert_eq!( - load_name_entry(deps.as_ref().storage, &name_fixture().name).unwrap_err(), - NameServiceError::NameNotFound { - name: name_fixture().name - } - ); - save(deps.as_mut().storage, &name_fixture()).unwrap(); - assert_eq!( - load_name_entry(deps.as_ref().storage, &name_fixture().name).unwrap(), - (1, name_fixture()) - ); - } - #[rstest] fn load_id_works(mut deps: TestDeps) { assert_eq!( load_id(deps.as_ref().storage, 1).unwrap_err(), NameServiceError::NotFound { name_id: 1 } ); - save(deps.as_mut().storage, &name_fixture()).unwrap(); - assert_eq!(load_id(deps.as_ref().storage, 1).unwrap(), name_fixture(),); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); + assert_eq!(load_id(deps.as_ref().storage, 1).unwrap(), name_fixture(1),); } #[rstest] fn load_name_works(mut deps: TestDeps) { assert_eq!( - load_name(deps.as_ref().storage, &name_fixture().name).unwrap_err(), + load_name(deps.as_ref().storage, name_fixture(1).entry()).unwrap_err(), NameServiceError::NameNotFound { - name: name_fixture().name + name: name_fixture(1).name.name, } ); - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert_eq!( - load_name(deps.as_ref().storage, &name_fixture().name).unwrap(), - name_fixture(), + load_name(deps.as_ref().storage, name_fixture(1).entry()).unwrap(), + name_fixture(1), ); } #[rstest] fn load_address_works(mut deps: TestDeps) { assert_eq!( - load_address(deps.as_ref().storage, &name_fixture().address).unwrap(), + load_address(deps.as_ref().storage, &name_fixture(1).name.address).unwrap(), vec![], ); - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert_eq!( - load_address(deps.as_ref().storage, &name_fixture().address).unwrap(), - vec![(1, name_fixture())], + load_address(deps.as_ref().storage, &name_fixture(1).name.address).unwrap(), + vec![name_fixture(1)], ); } #[rstest] fn load_owner_works(mut deps: TestDeps) { assert_eq!( - load_owner(deps.as_ref().storage, name_fixture().owner).unwrap(), + load_owner(deps.as_ref().storage, name_fixture(1).owner).unwrap(), vec![], ); - save(deps.as_mut().storage, &name_fixture()).unwrap(); + save(deps.as_mut().storage, &name_fixture(1)).unwrap(); assert_eq!( - load_owner(deps.as_ref().storage, name_fixture().owner).unwrap(), - vec![(1, name_fixture())], + load_owner(deps.as_ref().storage, name_fixture(1).owner).unwrap(), + vec![name_fixture(1)], ); } @@ -380,60 +356,19 @@ mod tests { assert_eq!( load_all_paged(&deps.storage, None, None).unwrap(), PagedLoad { - names: vec![(1, uniq_names[0].clone())], + names: vec![uniq_names[0].clone()], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(1), } ); } - #[rstest] - fn remove_id_works(mut deps: TestDeps) { - save(deps.as_mut().storage, &name_fixture()).unwrap(); - assert!(has_name_id(&deps.storage, 1)); - assert!(has_name(&deps.storage, &name_fixture().name)); - remove_id(deps.as_mut().storage, 1).unwrap(); - assert!(!has_name_id(&deps.storage, 1)); - assert!(!has_name(&deps.storage, &name_fixture().name)); - } - - #[rstest] - fn remove_name_works(mut deps: TestDeps) { - save(deps.as_mut().storage, &name_fixture()).unwrap(); - assert!(has_name_id(&deps.storage, 1)); - assert!(has_name(&deps.storage, &name_fixture().name)); - remove_name(deps.as_mut().storage, name_fixture().name).unwrap(); - assert!(!has_name_id(&deps.storage, 1)); - assert!(!has_name(&deps.storage, &name_fixture().name)); - } - #[rstest] fn save_set_of_unique_names_works(mut deps: TestDeps, uniq_names: Vec) { - let num = uniq_names.len() as NameId; - let ids = (1..=num).collect::>(); - assert_not_registered(&deps.storage, uniq_names.clone(), ids.clone()); - let saved_ids = save_all(deps.as_mut().storage, &uniq_names).unwrap(); - assert_eq!(saved_ids, ids); - assert_registered(&deps.storage, uniq_names.clone(), ids.clone()); - assert_only_these_registered(&deps.storage, uniq_names, ids); - } - - #[rstest] - fn save_set_of_unique_names_generates_ids(mut deps: TestDeps, uniq_names: Vec) { - let num = uniq_names.len() as NameId; - let ids = save_all(deps.as_mut().storage, &uniq_names).unwrap(); - assert_eq!(ids, (1..=num).collect::>()); - } - - #[rstest] - fn load_name_entry_for_unique_set_works(mut deps: TestDeps, uniq_names: Vec) { + assert_not_registered(&deps.storage, uniq_names.clone()); save_all(deps.as_mut().storage, &uniq_names).unwrap(); - for (id, name) in uniq_names.iter().enumerate() { - assert_eq!( - load_name_entry(deps.as_ref().storage, &name.name).unwrap(), - (id as NameId + 1, name.clone()), - ); - } + assert_registered(&deps.storage, uniq_names.clone()); + assert_only_these_registered(&deps.storage, uniq_names); } #[rstest] @@ -441,7 +376,7 @@ mod tests { save_all(deps.as_mut().storage, &uniq_names).unwrap(); for name in uniq_names { assert_eq!( - load_name(deps.as_ref().storage, &name.name).unwrap(), + load_name(deps.as_ref().storage, name.entry()).unwrap(), name.clone(), ); } @@ -452,17 +387,14 @@ mod tests { mut deps: TestDeps, uniq_names: Vec, ) { - let ids = save_all(deps.as_mut().storage, &uniq_names).unwrap(); - remove_id(deps.as_mut().storage, ids[1]).unwrap(); + save_all(deps.as_mut().storage, &uniq_names).unwrap(); + remove_id(deps.as_mut().storage, 2).unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - ( - 3, - name_fixture_full("three", "address_three", "owner_three") - ), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_three", "owner_three"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -476,16 +408,13 @@ mod tests { uniq_names: Vec, ) { save_all(deps.as_mut().storage, &uniq_names).unwrap(); - remove_name(deps.as_mut().storage, uniq_names[1].name.clone()).unwrap(); + remove_name(deps.as_mut().storage, uniq_names[1].entry().clone()).unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - ( - 3, - name_fixture_full("three", "address_three", "owner_three") - ), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_three", "owner_three"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -498,9 +427,9 @@ mod tests { mut deps: TestDeps, overlapping_addresses: Vec, ) { - let ids = save_all(deps.as_mut().storage, &overlapping_addresses).unwrap(); - assert_registered(&deps.storage, overlapping_addresses.clone(), ids.clone()); - assert_only_these_registered(&deps.storage, overlapping_addresses, ids); + save_all(deps.as_mut().storage, &overlapping_addresses).unwrap(); + assert_registered(&deps.storage, overlapping_addresses.clone()); + assert_only_these_registered(&deps.storage, overlapping_addresses); } #[rstest] @@ -513,9 +442,9 @@ mod tests { load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (2, name_fixture_full("two", "address_two", "owner_two")), - (3, name_fixture_full("three", "address_two", "owner_three")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_two", "owner_three"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -524,8 +453,8 @@ mod tests { assert_eq!( load_address(deps.as_ref().storage, &Address::new("address_two")).unwrap(), vec![ - (2, name_fixture_full("two", "address_two", "owner_two")), - (3, name_fixture_full("three", "address_two", "owner_three")), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_two", "owner_three"), ] ); } @@ -535,14 +464,14 @@ mod tests { mut deps: TestDeps, overlapping_addresses: Vec, ) { - let ids = save_all(deps.as_mut().storage, &overlapping_addresses).unwrap(); - remove_id(deps.as_mut().storage, ids[1]).unwrap(); + save_all(deps.as_mut().storage, &overlapping_addresses).unwrap(); + remove_id(deps.as_mut().storage, 2).unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (3, name_fixture_full("three", "address_two", "owner_three")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_two", "owner_three"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -556,13 +485,17 @@ mod tests { overlapping_addresses: Vec, ) { save_all(deps.as_mut().storage, &overlapping_addresses).unwrap(); - remove_name(deps.as_mut().storage, overlapping_addresses[1].name.clone()).unwrap(); + remove_name( + deps.as_mut().storage, + overlapping_addresses[1].name.name.clone(), + ) + .unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (3, name_fixture_full("three", "address_two", "owner_three")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_two", "owner_three"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -575,9 +508,9 @@ mod tests { mut deps: TestDeps, overlapping_owners: Vec, ) { - let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); - assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone()); - assert_only_these_registered(&deps.storage, overlapping_owners, ids); + save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); + assert_registered(&deps.storage, overlapping_owners.clone()); + assert_only_these_registered(&deps.storage, overlapping_owners); } #[rstest] @@ -590,9 +523,9 @@ mod tests { load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (2, name_fixture_full("two", "address_two", "owner_two")), - (3, name_fixture_full("three", "address_three", "owner_two")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_three", "owner_two"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -601,8 +534,8 @@ mod tests { assert_eq!( load_owner(deps.as_ref().storage, Addr::unchecked("owner_two")).unwrap(), vec![ - (2, name_fixture_full("two", "address_two", "owner_two")), - (3, name_fixture_full("three", "address_three", "owner_two")), + name_fixture_full(2, "two", "address_two", "owner_two"), + name_fixture_full(3, "three", "address_three", "owner_two"), ] ); } @@ -612,16 +545,16 @@ mod tests { mut deps: TestDeps, overlapping_owners: Vec, ) { - let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); - assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone()); - assert_only_these_registered(&deps.storage, overlapping_owners, ids); + save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); + assert_registered(&deps.storage, overlapping_owners.clone()); + assert_only_these_registered(&deps.storage, overlapping_owners); remove_id(deps.as_mut().storage, 2).unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (3, name_fixture_full("three", "address_three", "owner_two")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_three", "owner_two"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -634,16 +567,16 @@ mod tests { mut deps: TestDeps, overlapping_owners: Vec, ) { - let ids = save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); - assert_registered(&deps.storage, overlapping_owners.clone(), ids.clone()); - assert_only_these_registered(&deps.storage, overlapping_owners.clone(), ids); - remove_name(deps.as_mut().storage, overlapping_owners[1].name.clone()).unwrap(); + save_all(deps.as_mut().storage, &overlapping_owners).unwrap(); + assert_registered(&deps.storage, overlapping_owners.clone()); + assert_only_these_registered(&deps.storage, overlapping_owners.clone()); + remove_name(deps.as_mut().storage, overlapping_owners[1].entry().clone()).unwrap(); assert_eq!( load_all_paged(deps.as_ref().storage, None, None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (3, name_fixture_full("three", "address_three", "owner_two")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(3, "three", "address_three", "owner_two"), ], limit: NAME_DEFAULT_RETRIEVAL_LIMIT as usize, start_next_after: Some(3), @@ -658,8 +591,8 @@ mod tests { load_all_paged(&deps.storage, Some(2), None).unwrap(), PagedLoad { names: vec![ - (1, name_fixture_full("one", "address_one", "owner_one")), - (2, name_fixture_full("two", "address_two", "owner_two")), + name_fixture_full(1, "one", "address_one", "owner_one"), + name_fixture_full(2, "two", "address_two", "owner_two"), ], limit: 2, start_next_after: Some(2), @@ -668,10 +601,12 @@ mod tests { assert_eq!( load_all_paged(&deps.storage, Some(1), Some(2)).unwrap(), PagedLoad { - names: vec![( + names: vec![name_fixture_full( 3, - name_fixture_full("three", "address_three", "owner_three") - ),], + "three", + "address_three", + "owner_three" + )], limit: 1, start_next_after: Some(3), } @@ -679,10 +614,12 @@ mod tests { assert_eq!( load_all_paged(&deps.storage, Some(2), Some(2)).unwrap(), PagedLoad { - names: vec![( + names: vec![name_fixture_full( 3, - name_fixture_full("three", "address_three", "owner_three") - ),], + "three", + "address_three", + "owner_three" + )], limit: 2, start_next_after: Some(3), } diff --git a/contracts/name-service/src/state/nonce.rs b/contracts/name-service/src/state/nonce.rs new file mode 100644 index 0000000000..bf865af82a --- /dev/null +++ b/contracts/name-service/src/state/nonce.rs @@ -0,0 +1,71 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::{constants::SIGNING_NONCES_NAMESPACE, Result}; + +use cosmwasm_std::{Addr, Storage}; +use cw_storage_plus::Map; +use nym_contracts_common::signing::Nonce; + +pub const NONCES: Map<'_, Addr, Nonce> = Map::new(SIGNING_NONCES_NAMESPACE); + +pub fn get_signing_nonce(storage: &dyn Storage, address: Addr) -> Result { + let nonce = NONCES.may_load(storage, address)?.unwrap_or(0); + Ok(nonce) +} + +fn update_signing_nonce(storage: &mut dyn Storage, address: Addr, value: Nonce) -> Result<()> { + NONCES + .save(storage, address, &value) + .map_err(|err| err.into()) +} + +pub fn increment_signing_nonce(storage: &mut dyn Storage, address: Addr) -> Result<()> { + // get the current nonce + let nonce = get_signing_nonce(storage, address.clone())?; + + // increment it for the next use + update_signing_nonce(storage, address, nonce + 1) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_helpers::transactions::instantiate_test_contract; + use cosmwasm_std::{ + testing::{MockApi, MockQuerier}, + MemoryStorage, OwnedDeps, + }; + use rstest::rstest; + + type TestDeps = OwnedDeps; + + #[rstest::fixture] + fn deps() -> TestDeps { + instantiate_test_contract() + } + + fn addr(s: &str) -> Addr { + Addr::unchecked(s) + } + + #[rstest] + fn getting_signing_nonce_doesnt_increment_it(deps: TestDeps) { + assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0); + assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0); + } + + #[rstest] + fn increment_works(mut deps: TestDeps) { + assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 0); + increment_signing_nonce(&mut deps.storage, addr("gunnar")).unwrap(); + assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 1); + } + + #[rstest] + fn incrementing_is_independent(mut deps: TestDeps) { + increment_signing_nonce(&mut deps.storage, addr("gunnar")).unwrap(); + assert_eq!(get_signing_nonce(&deps.storage, addr("gunnar")).unwrap(), 1); + assert_eq!(get_signing_nonce(&deps.storage, addr("bjorn")).unwrap(), 0); + } +} diff --git a/contracts/name-service/src/test_helpers/assert.rs b/contracts/name-service/src/test_helpers/assert.rs index b9c862906b..1f2475b196 100644 --- a/contracts/name-service/src/test_helpers/assert.rs +++ b/contracts/name-service/src/test_helpers/assert.rs @@ -1,11 +1,12 @@ use cosmwasm_std::{from_binary, testing::mock_env, Addr, Coin, Deps}; +use nym_contracts_common::signing::Nonce; use nym_name_service_common::{ msg::QueryMsg, response::{ConfigResponse, PagedNamesListResponse}, - NameEntry, NameId, + NameId, RegisteredName, }; -use crate::{constants::NAME_DEFAULT_RETRIEVAL_LIMIT, error::NameServiceError}; +use crate::{constants::NAME_DEFAULT_RETRIEVAL_LIMIT, NameServiceError}; pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) { crate::state::assert_admin(deps, admin).unwrap(); @@ -14,10 +15,10 @@ pub fn assert_config(deps: Deps, admin: &Addr, deposit_required: Coin) { assert_eq!(config, ConfigResponse { deposit_required }); } -pub fn assert_names(deps: Deps, expected_names: &[NameEntry]) { +pub fn assert_names(deps: Deps, expected_names: &[RegisteredName]) { let res = crate::contract::query(deps, mock_env(), QueryMsg::all()).unwrap(); let names: PagedNamesListResponse = from_binary(&res).unwrap(); - let start_next_after = expected_names.iter().last().map(|s| s.name_id); + let start_next_after = expected_names.iter().last().map(|s| s.id); assert_eq!( names, PagedNamesListResponse { @@ -28,16 +29,16 @@ pub fn assert_names(deps: Deps, expected_names: &[NameEntry]) { ); } -pub fn assert_name(deps: Deps, expected_name: &NameEntry) { +pub fn assert_name(deps: Deps, expected_name: &RegisteredName) { let res = crate::contract::query( deps, mock_env(), QueryMsg::NameId { - name_id: expected_name.name_id, + name_id: expected_name.id, }, ) .unwrap(); - let names: NameEntry = from_binary(&res).unwrap(); + let names: RegisteredName = from_binary(&res).unwrap(); assert_eq!(&names, expected_name); } @@ -63,3 +64,16 @@ pub fn assert_not_found(deps: Deps, expected_id: NameId) { } )); } + +pub fn assert_current_nonce(deps: Deps, address: &Addr, expected_nonce: Nonce) { + let res = crate::contract::query( + deps, + mock_env(), + QueryMsg::SigningNonce { + address: address.to_string(), + }, + ) + .unwrap(); + let nonce: Nonce = from_binary(&res).unwrap(); + assert_eq!(nonce, expected_nonce); +} diff --git a/contracts/name-service/src/test_helpers/fixture.rs b/contracts/name-service/src/test_helpers/fixture.rs index 3df82f6086..5c24ece5d8 100644 --- a/contracts/name-service/src/test_helpers/fixture.rs +++ b/contracts/name-service/src/test_helpers/fixture.rs @@ -1,29 +1,103 @@ -use cosmwasm_std::Addr; -use nym_name_service_common::{Address, NameEntry, NameId, NymName, RegisteredName}; +use cosmwasm_std::{Addr, Coin, DepsMut}; +use nym_contracts_common::{signing::MessageSignature, IdentityKeyRef}; +use nym_crypto::asymmetric::identity; +use nym_name_service_common::{Address, NameDetails, NameId, NymName, RegisteredName}; +use rand_chacha::rand_core::{CryptoRng, RngCore}; -use super::helpers::nyms; +use super::{ + helpers::nyms, + signing::{ed25519_sign_message, name_register_sign_payload}, +}; -pub fn name_fixture_full(name: &str, nym_address: &str, owner: &str) -> RegisteredName { +pub fn new_name( + name_id: NameId, + name: &NymName, + address: &Address, + owner: &Addr, + identity_key: IdentityKeyRef, +) -> RegisteredName { RegisteredName { - name: NymName::new(name).unwrap(), - address: Address::new(nym_address), - owner: Addr::unchecked(owner), + id: name_id, + name: NameDetails { + name: name.clone(), + address: address.clone(), + identity_key: identity_key.to_string(), + }, + owner: owner.clone(), block_height: 12345, deposit: nyms(100), } } -pub fn name_fixture() -> RegisteredName { - name_fixture_full("my-service", "client_id.client_key@gateway_id", "steve") +pub fn name_fixture(id: NameId) -> RegisteredName { + new_name( + id, + &NymName::new("my-service").unwrap(), + &Address::new("client_id.client_key@gateway_id"), + &Addr::unchecked("steve"), + "identity", + ) } -pub fn name_fixture_name(name: &str) -> RegisteredName { - name_fixture_full(name, "client_id.client_key@gateway_id", "steve") +#[allow(unused)] +pub fn name_fixture_with_name(id: NameId, name: &str, address: &str) -> RegisteredName { + new_name( + id, + &NymName::new(name).unwrap(), + &Address::new(address), + &Addr::unchecked("steve"), + "identity", + ) } -pub fn name_entry(name_id: NameId, name: NymName, address: Address, owner: Addr) -> NameEntry { - NameEntry { - name_id, - name: name_fixture_full(name.as_str(), address.as_str(), owner.as_str()), - } +pub fn name_fixture_full(id: NameId, name: &str, address: &str, owner: &str) -> RegisteredName { + new_name( + id, + &NymName::new(name).unwrap(), + &Address::new(address), + &Addr::unchecked(owner), + "identity", + ) +} + +// Create a new name, using a correctly generted identity key +pub fn new_name_details( + rng: &mut R, + name: &str, + nym_address: &str, +) -> (NameDetails, identity::KeyPair) +where + R: RngCore + CryptoRng, +{ + let keypair = identity::KeyPair::new(rng); + ( + NameDetails { + name: NymName::new(name).unwrap(), + address: Address::new(nym_address), + identity_key: keypair.public_key().to_base58_string(), + }, + keypair, + ) +} + +// Create a new service, with a correctly generated identity key, and sign it +pub fn new_name_details_with_sign( + deps: DepsMut<'_>, + rng: &mut R, + name: &str, + nym_address: &str, + owner: &str, + deposit: Coin, +) -> (NameDetails, MessageSignature) +where + R: RngCore + CryptoRng, +{ + // Service + let (name, keypair) = new_name_details(rng, name, nym_address); + + // Sign + let sign_msg = name_register_sign_payload(deps.as_ref(), owner, name.clone(), deposit); + let owner_signature = ed25519_sign_message(sign_msg, keypair.private_key()); + + (name, owner_signature) } diff --git a/contracts/name-service/src/test_helpers/helpers.rs b/contracts/name-service/src/test_helpers/helpers.rs index 0bd4f2ef5f..145802e061 100644 --- a/contracts/name-service/src/test_helpers/helpers.rs +++ b/contracts/name-service/src/test_helpers/helpers.rs @@ -1,19 +1,16 @@ -use cosmwasm_std::{ - coin, coins, - testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}, - Coin, DepsMut, Event, MemoryStorage, OwnedDeps, Response, -}; +use cosmwasm_std::{Coin, Event, Response}; use cw_multi_test::AppResponse; -use nym_name_service_common::{ - events::{NameEventType, NAME_ID}, - msg::{ExecuteMsg, InstantiateMsg}, - NameId, NymName, RegisteredName, -}; +use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; pub fn nyms(amount: u64) -> Coin { Coin::new(amount.into(), "unym") } +pub fn test_rng() -> ChaCha20Rng { + let dummy_seed = [42u8; 32]; + ChaCha20Rng::from_seed(dummy_seed) +} + pub fn get_event_types(response: &Response, event_type: &str) -> Vec { response .events @@ -55,53 +52,3 @@ pub fn get_app_attribute(response: &AppResponse, event_type: &str, key: &str) -> .value .clone() } - -#[allow(unused)] -pub fn get_app_attributes(response: &AppResponse, event_type: &str, key: &str) -> Vec { - get_app_event_types(response, event_type) - .iter() - .map(|ev| { - ev.attributes - .iter() - .find(|attr| attr.key == key) - .unwrap() - .value - .clone() - }) - .collect::>() -} - -pub fn instantiate_test_contract() -> OwnedDeps { - let mut deps = mock_dependencies(); - let msg = InstantiateMsg { - deposit_required: coin(100, "unym"), - }; - let env = mock_env(); - let info = mock_info("creator", &[]); - let res = crate::instantiate(deps.as_mut(), env, info, msg).unwrap(); - assert_eq!(res.messages.len(), 0); - deps -} - -pub fn register_name(deps: DepsMut<'_>, name: &RegisteredName) -> NameId { - let msg: ExecuteMsg = name.clone().into(); - let info = mock_info(name.owner.as_str(), &coins(100, "unym")); - let res = crate::execute(deps, mock_env(), info, msg).unwrap(); - let name_id: NameId = get_attribute(&res, &NameEventType::Register.to_string(), NAME_ID) - .parse() - .unwrap(); - name_id -} - -pub fn delete_name_id(deps: DepsMut<'_>, name_id: NameId, owner: &str) { - let msg = ExecuteMsg::DeleteId { name_id }; - let info = mock_info(owner, &[]); - crate::execute(deps, mock_env(), info, msg).unwrap(); -} - -#[allow(unused)] -pub fn delete_name(deps: DepsMut<'_>, name: NymName, owner: &str) { - let msg = ExecuteMsg::DeleteName { name }; - let info = mock_info(owner, &[]); - crate::execute(deps, mock_env(), info, msg).unwrap(); -} diff --git a/contracts/name-service/src/test_helpers/mod.rs b/contracts/name-service/src/test_helpers/mod.rs index b1e4a18e01..ee0717f4e4 100644 --- a/contracts/name-service/src/test_helpers/mod.rs +++ b/contracts/name-service/src/test_helpers/mod.rs @@ -1,4 +1,5 @@ pub mod assert; pub mod fixture; pub mod helpers; -pub mod test_setup; +pub mod signing; +pub mod transactions; diff --git a/contracts/name-service/src/test_helpers/signing.rs b/contracts/name-service/src/test_helpers/signing.rs new file mode 100644 index 0000000000..d7431fc7ed --- /dev/null +++ b/contracts/name-service/src/test_helpers/signing.rs @@ -0,0 +1,39 @@ +use cosmwasm_std::{Addr, Coin, Deps}; +use nym_contracts_common::signing::{ + MessageSignature, SignableMessage, SigningAlgorithm, SigningPurpose, +}; +use nym_crypto::asymmetric::identity; +use nym_name_service_common::{ + signing_types::{construct_name_register_sign_payload, SignableNameRegisterMsg}, + NameDetails, +}; +use serde::Serialize; + +use crate::state; + +pub fn name_register_sign_payload( + deps: Deps<'_>, + owner: &str, + name: NameDetails, + deposit: Coin, +) -> SignableNameRegisterMsg { + let owner = Addr::unchecked(owner); + let nonce = state::get_signing_nonce(deps.storage, owner.clone()).unwrap(); + construct_name_register_sign_payload(nonce, owner, deposit, name) +} + +pub fn ed25519_sign_message( + message: SignableMessage, + private_key: &identity::PrivateKey, +) -> MessageSignature { + match message.algorithm { + SigningAlgorithm::Ed25519 => { + let plaintext = message.to_plaintext().unwrap(); + let signature = private_key.sign(&plaintext); + MessageSignature::from(signature.to_bytes().as_ref()) + } + SigningAlgorithm::Secp256k1 => { + unimplemented!() + } + } +} diff --git a/contracts/name-service/src/test_helpers/transactions.rs b/contracts/name-service/src/test_helpers/transactions.rs new file mode 100644 index 0000000000..6458ad5b3e --- /dev/null +++ b/contracts/name-service/src/test_helpers/transactions.rs @@ -0,0 +1,72 @@ +use cosmwasm_std::{ + coin, + testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}, + DepsMut, MemoryStorage, OwnedDeps, +}; +use nym_name_service_common::{ + events::{NameEventType, NAME_ID}, + msg::{ExecuteMsg, InstantiateMsg}, + NameDetails, NameId, NymName, +}; +use rand_chacha::rand_core::{CryptoRng, RngCore}; + +use super::helpers::{get_attribute, nyms}; + +pub fn instantiate_test_contract() -> OwnedDeps { + let mut deps = mock_dependencies(); + let msg = InstantiateMsg { + deposit_required: coin(100, "unym"), + }; + let env = mock_env(); + let info = mock_info("creator", &[]); + let res = crate::instantiate(deps.as_mut(), env, info, msg).unwrap(); + assert_eq!(res.messages.len(), 0); + deps +} + +pub fn register_name( + mut deps: DepsMut<'_>, + rng: &mut R, + name: &str, + nym_address: &str, + owner: &str, +) -> (NameId, NameDetails) +where + R: RngCore + CryptoRng, +{ + let deposit = nyms(100); + let (name, owner_signature) = super::fixture::new_name_details_with_sign( + deps.branch(), + rng, + name, + nym_address, + owner, + deposit.clone(), + ); + + // Register + let msg = ExecuteMsg::Register { + name: name.clone(), + owner_signature, + }; + let info = mock_info(owner, &[deposit]); + let res = crate::execute(deps, mock_env(), info, msg).unwrap(); + + let name_id: NameId = get_attribute(&res, &NameEventType::Register.to_string(), NAME_ID) + .parse() + .unwrap(); + (name_id, name) +} + +pub fn delete_name_id(deps: DepsMut<'_>, name_id: NameId, owner: &str) { + let msg = ExecuteMsg::DeleteId { name_id }; + let info = mock_info(owner, &[]); + crate::execute(deps, mock_env(), info, msg).unwrap(); +} + +#[allow(unused)] +pub fn delete_name(deps: DepsMut<'_>, name: NymName, owner: &str) { + let msg = ExecuteMsg::DeleteName { name }; + let info = mock_info(owner, &[]); + crate::execute(deps, mock_env(), info, msg).unwrap(); +} diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index 3f79b96a6e..3b81f82751 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -79,7 +79,7 @@ copy-fonts = true no-section-label = false additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "last-changed.css"] additional-js = ["theme/pagetoc.js"] -git-repository-url = "https://github.com/nymtech/dev-portal" +git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" # edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" site-url = "/developers/" diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index a2e6cbeb6f..be35e9c30a 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -14,6 +14,11 @@ - [SOCKS Proxy (CLI)](quickstart/socks-proxy.md) - [NymConnect Beta (GUI)](quickstart/nymconnect-gui.md) +# User Manuals + +- [NymConnect Monero](tutorials/monero.md) +- [NymConnect Matrix](tutorials/matrix.md) + # Integrations - [Integration Options](integrations/integration-options.md) @@ -39,10 +44,10 @@ - [Community Guides](community-resources/community-guides.md) - [Change Service Grantee Information](info-request.md) - [Rewards FAQ](community-resources/rewards-faq.md) ---- +--- # Misc. - [Glossary](glossary.md) - [Code of Conduct](coc.md) - [Licensing](licensing.md) ---- +--- diff --git a/documentation/dev-portal/src/images/element_nym_keybind.png b/documentation/dev-portal/src/images/element_nym_keybind.png new file mode 100644 index 0000000000..d0bd383494 Binary files /dev/null and b/documentation/dev-portal/src/images/element_nym_keybind.png differ diff --git a/documentation/dev-portal/src/images/monero_tutorial/monero.png b/documentation/dev-portal/src/images/monero_tutorial/monero.png new file mode 100644 index 0000000000..3c7fadd32a Binary files /dev/null and b/documentation/dev-portal/src/images/monero_tutorial/monero.png differ diff --git a/documentation/dev-portal/src/tutorials/matrix.md b/documentation/dev-portal/src/tutorials/matrix.md new file mode 100644 index 0000000000..39852fed05 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/matrix.md @@ -0,0 +1,44 @@ +# Matrix NymConnect Integration + + +Chat applications became an essential part of human communication. Matrix chat has end to end encryption on protocol level and Element app users can sort their communication into spaces and rooms. Now the Matrix communities can rely on network privacy as NymConnect supports Matrix chat protocol. + +Currently there is no option in Matrix's Element client to set a socks5 proxy. In order to use Element via NymConnect users have to start it from the command-line. The setup is simple, for convenience a a keyboard shortcut setting can be easily done. + + +## Setup & Run + +Make sure you have installed and started **[NymConnect](https://nymtech.net/developers/quickstart/nymconnect-gui.html)** on your desktop. + +To then start Matrix's Element client via a Socks5 proxy connected to NymConnect, open terminal and run: + +```sh +element-desktop --proxy-server=socks5://127.0.0.1:1080 +``` + +## Optimise setup with a keybinding / alias + +### Keybinding +An eloquent solution to avoid entering a command every time is to setup your keybinding. Open your settings, navigate to `Keyboard Shortcuts` and choose to `Set Custom Shortcut`. `Name` and `Shortcut` fields are up to your preference, to the `Command` line add: + +```sh +element-desktop --proxy-server=socks5://127.0.0.1:1080 +``` +Make sure your `Shortcut` isn't already taken by something else in the menu. + +An example can look like this. + +![](../images/element_nym_keybind.png) + +Alternatively you can add a keybinding via the CLI, using whatever config files you edit for your given desktop environment / window manager. + +### Create an alias +If you prefer to simply shorten the length of the command (or all your keybindings are already taken) then you can simply create an alias for this long-winded command (this example aliases that command to the single word `element`, but you can replace it with whatever you like): + +```sh +alias element="element-desktop --proxy-server=socks5://127.0.0.1:1080" +``` + +To make this alias persist, then add this to your `.bashrc` or `.zshrc` file (usually located in your `$HOME` directory) and `source` that file. + +Now you can run Element throught the mixnet with a single-word command. diff --git a/documentation/dev-portal/src/tutorials/monero.md b/documentation/dev-portal/src/tutorials/monero.md new file mode 100644 index 0000000000..77439d0728 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/monero.md @@ -0,0 +1,42 @@ +# Monero NymConnect Integration + +*New Nym mixnet integration launched for Monero desktop to secure the right to financial privacy and censorship-resistance* + +![](../images/monero_tutorial/monero.png) + +Financial privacy is an important component of digital currencies and the use of Nym will provide users with the highest level of privacy at the infrastructure level. All users of digital currencies should be afforded equal rights to protection from financial surveillance. + +A team made up of Monero community members have successfully set up a service provider to use Monero (using the Monero desktop wallet) over the Nym mixnet. This allows Monero users to easily use NymConnect to run Monero over the mixnet, thereby enhancing the privacy of Monero transactions. + +## How can I use Monero over the Nym mixnet? + +The mainnet service provider to Monero over the Nym mixnet is now ready for use via [NymConnect](https://nymtech.net/download-nymconnect/). + +* Download and open the latest version of [NymConnect](https://nymtech.net/download-nymconnect/). +* Click on the top left options and go to Settings +* Go to “Select service provider” and turn it on +* For Mainnet, search for this provider or insert it manually: + +```sh +i1TiuoNp4jp9weffCW7tPnkb4hRTPydRjX8iXFVaYDG.88Z1hruuvbzWpdCE2xYnTbPNrr49j4s7mmUQC5wvRRLZ@3EPuxwGn2WP2HdxybzoDa5QsohYSP76aQQRUJuPMvk23 +``` + +* Go to the main NymConnect interface and connect to the mixnet + +Then go to your Monero wallet (gui or otherwise) and change the settings to run over socks5 proxy: + +**Monero desktop:** + +* Settings -> Interface -> Socks5 proxy -> Add values: IP address `localhost`, Port `1080` + + + +**CLI** + +* **Monerod:** add `--proxy 127.0.0.1:1080 --bootstrap-daemon-proxy 127.0.0.1:1080` to args + +* **Monero-wallet-{rpc, cli}:** add `--proxy 127.0.0.1:1080 --daemon-ssl-allow-any-cert` to args + +Follow the instructions and the Monero mainnet will be connected through to the Nym mixnet. + +For those who want to try it out in testnet, a stagenet service provider is also available: [https://nymtech.net/.wellknown/connect/service-providers.json](https://nymtech.net/.wellknown/connect/service-providers.json) diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index c27cde717e..b70997a556 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -48,9 +48,9 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" - -# old variables - this is still needed for links.. TODO change to develop? -platform_release_version = "v1.1.21" +# vars for links: TODO think on how to streamline updating +platform_release_version = "v1.1.22" +wallet_release_version = "v1.2.5" [preprocessor.last-changed] command = "mdbook-last-changed" @@ -87,7 +87,7 @@ copy-fonts = true no-section-label = false additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "last-changed.css"] additional-js = ["theme/pagetoc.js"] -git-repository-url = "https://github.com/nymtech/docs" +git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" # edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}" # site-url = "/docs/" diff --git a/documentation/docs/src/sdk/rust.md b/documentation/docs/src/sdk/rust.md index 1408d85faa..ba1c079725 100644 --- a/documentation/docs/src/sdk/rust.md +++ b/documentation/docs/src/sdk/rust.md @@ -50,7 +50,7 @@ As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform the actions taken automatically above (`examples/manually_handle_keys_and_config.rs`) ```rust,noplayground -{{#include ../../../../sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs}} +{{#include ../../../../sdk/rust/nym-sdk/examples/manually_handle_storage.rs}} ``` ### Anonymous replies with SURBs diff --git a/explorer/.nvmrc b/explorer/.nvmrc index b6a7d89c68..3c032078a4 100644 --- a/explorer/.nvmrc +++ b/explorer/.nvmrc @@ -1 +1 @@ -16 +18 diff --git a/lerna.json b/lerna.json index 5d17094fd5..851bdf0b54 100644 --- a/lerna.json +++ b/lerna.json @@ -1,10 +1,4 @@ { - "packages": [ - "ts-packages/*", - "nym-wallet", - "nym-connect/**", - "sdk/typescript/examples/docs", - "sdk/typescript/packages/**" - ], - "version": "0.0.0" + "version": "0.0.0", + "useWorkspaces": true } diff --git a/nym-api/src/network_monitor/monitor/preparer.rs b/nym-api/src/network_monitor/monitor/preparer.rs index b873669f99..8b49055476 100644 --- a/nym-api/src/network_monitor/monitor/preparer.rs +++ b/nym-api/src/network_monitor/monitor/preparer.rs @@ -250,24 +250,24 @@ impl PacketPreparer { let mut routes = Vec::new(); for i in 0..most_available { let Ok(node_1) = self.try_parse_mix_bond(rand_l1[i]) else { - blacklist.insert(rand_l1[i].identity().to_owned()); - continue - }; + blacklist.insert(rand_l1[i].identity().to_owned()); + continue; + }; let Ok(node_2) = self.try_parse_mix_bond(rand_l2[i]) else { - blacklist.insert(rand_l2[i].identity().to_owned()); - continue - }; + blacklist.insert(rand_l2[i].identity().to_owned()); + continue; + }; let Ok(node_3) = self.try_parse_mix_bond(rand_l3[i]) else { - blacklist.insert(rand_l3[i].identity().to_owned()); - continue - }; + blacklist.insert(rand_l3[i].identity().to_owned()); + continue; + }; let Ok(gateway) = self.try_parse_gateway_bond(rand_gateways[i]) else { - blacklist.insert(rand_gateways[i].identity().to_owned()); - continue - }; + blacklist.insert(rand_gateways[i].identity().to_owned()); + continue; + }; routes.push(TestRoute::new(rng.gen(), node_1, node_2, node_3, gateway)) } diff --git a/nym-api/src/nym_contract_cache/cache/data.rs b/nym-api/src/nym_contract_cache/cache/data.rs index 235afdb630..27285db41a 100644 --- a/nym-api/src/nym_contract_cache/cache/data.rs +++ b/nym-api/src/nym_contract_cache/cache/data.rs @@ -6,7 +6,7 @@ use nym_mixnet_contract_common::{ families::FamilyHead, GatewayBond, IdentityKey, Interval, MixId, MixNodeDetails, RewardingParams, }; -use nym_name_service_common::NameEntry; +use nym_name_service_common::RegisteredName; use nym_service_provider_directory_common::Service; use std::collections::HashSet; @@ -26,7 +26,7 @@ pub(crate) struct ValidatorCacheData { pub(crate) mix_to_family: Cache>, pub(crate) service_providers: Cache>, - pub(crate) registered_names: Cache>, + pub(crate) registered_names: Cache>, } impl ValidatorCacheData { diff --git a/nym-api/src/nym_contract_cache/cache/mod.rs b/nym-api/src/nym_contract_cache/cache/mod.rs index 13e2783e47..2eac4f0c61 100644 --- a/nym-api/src/nym_contract_cache/cache/mod.rs +++ b/nym-api/src/nym_contract_cache/cache/mod.rs @@ -5,7 +5,7 @@ use nym_mixnet_contract_common::{ families::FamilyHead, GatewayBond, IdentityKey, Interval, MixId, MixNodeBond, MixNodeDetails, RewardingParams, }; -use nym_name_service_common::NameEntry; +use nym_name_service_common::RegisteredName; use nym_service_provider_directory_common::Service; use rocket::fairing::AdHoc; use std::{ @@ -53,7 +53,7 @@ impl NymContractCache { current_interval: Interval, mix_to_family: Vec<(IdentityKey, FamilyHead)>, services: Option>, - names: Option>, + names: Option>, ) { match time::timeout(Duration::from_millis(100), self.inner.write()).await { Ok(mut cache) => { @@ -303,7 +303,7 @@ impl NymContractCache { } } - pub(crate) async fn names(&self) -> Cache> { + pub(crate) async fn names(&self) -> Cache> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.registered_names.clone(), Err(err) => { diff --git a/nym-connect/desktop/.nvmrc b/nym-connect/desktop/.nvmrc index da2d3988d7..25bf17fc5a 100644 --- a/nym-connect/desktop/.nvmrc +++ b/nym-connect/desktop/.nvmrc @@ -1 +1 @@ -14 \ No newline at end of file +18 \ No newline at end of file diff --git a/nym-connect/desktop/CHANGELOG.md b/nym-connect/desktop/CHANGELOG.md index 42d923e76a..174b5e938d 100644 --- a/nym-connect/desktop/CHANGELOG.md +++ b/nym-connect/desktop/CHANGELOG.md @@ -2,16 +2,18 @@ ## [Unreleased] -## [v1.1.14] (2023-06-27) +## [v1.1.14] (2023-07-04) - Nym connect fails to start when encountering an old config version ([#3588]) - NC desktop - apps section adjustments + add monero integration ([#2977]) +- nym-connect: use different service provider directory when medium toggle enabled ([#3617]) - Fix medium toggle in nym-connect ([#3590]) - [bugfix] NC: load old gateway configuration if we're not registering ([#3586]) - nym-connect: medium speed setting ([#3585]) [#3588]: https://github.com/nymtech/nym/issues/3588 [#2977]: https://github.com/nymtech/nym/issues/2977 +[#3617]: https://github.com/nymtech/nym/pull/3617 [#3590]: https://github.com/nymtech/nym/pull/3590 [#3586]: https://github.com/nymtech/nym/pull/3586 [#3585]: https://github.com/nymtech/nym/pull/3585 diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index ff9619f33a..db29e48244 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3657,8 +3657,12 @@ name = "nym-name-service-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", "schemars", "serde", + "thiserror", ] [[package]] diff --git a/nym-connect/desktop/package.json b/nym-connect/desktop/package.json index df00561a19..60ea3b4830 100644 --- a/nym-connect/desktop/package.json +++ b/nym-connect/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@nym/nym-connect", - "version": "1.1.14", + "version": "1.1.13", "main": "index.js", "license": "MIT", "scripts": { diff --git a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs index a518dc29b1..6195d41283 100644 --- a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs @@ -10,39 +10,58 @@ use nym_contracts_common::types::Percent; static SERVICE_PROVIDER_WELLKNOWN_URL: &str = "https://nymtech.net/.wellknown/connect/service-providers.json"; +// List of network-requesters running with medium toggle enabled, for testing +static SERVICE_PROVIDER_WELLKNOWN_URL_MEDIUM: &str = + "https://nymtech.net/.wellknown/connect/service-providers-medium.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"; +fn get_services_url() -> &'static str { + std::env::var("NYM_CONNECT_ENABLE_MEDIUM") + .is_ok() + .then(|| SERVICE_PROVIDER_WELLKNOWN_URL_MEDIUM) + .unwrap_or(SERVICE_PROVIDER_WELLKNOWN_URL) +} + #[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); + let all_services = fetch_services().await?; + log::trace!("Received: {:#?}", all_services); - log::trace!("Fetching gateways"); - let gateway_res = reqwest::get(GATEWAYS_DETAILED_URL) - .await? - .json::>() - .await?; - log::trace!("Received: {:#?}", gateway_res); + // Early return if we're running with medium toggle enabled + if std::env::var("NYM_CONNECT_ENABLE_MEDIUM").is_ok() { + return Ok(all_services.into_iter().flat_map(|sp| sp.items).collect()); + } // TODO: get paged log::trace!("Fetching active services"); - let active_services = reqwest::get(HARBOUR_MASTER_URL) - .await? - .json::>() - .await?; + let active_services = fetch_active_services().await?; log::trace!("Active: {:#?}", active_services); - let mut filtered: Vec = vec![]; + let filtered_services = filter_out_inactive(all_services, active_services); - for service in &services_res { - let items = service + log::trace!("Fetching gateways"); + let gateway_res = get_gateways_detailed().await?; + log::trace!("Received: {:#?}", gateway_res); + + // Use only services that are active AND have a performance of >= 90% + let filtered_services_with_good_gateway = + filter_out_poor_gateways(filtered_services, gateway_res); + + Ok(filtered_services_with_good_gateway) +} + +fn filter_out_inactive( + services_res: Vec, + active_services: PagedResult, +) -> Vec { + let mut filtered: Vec = vec![]; + for service_type in &services_res { + let items = service_type .items .clone() .into_iter() @@ -53,33 +72,48 @@ pub async fn get_services() -> Result> { .any(|active| active.service_provider_client_id == sp.address) }) .collect_vec(); - log::trace!("service = {} has {} items", service.id, items.len()); + log::trace!("service = {} has {} items", service_type.id, items.len()); filtered.push(DirectoryService { - id: service.id.clone(), - description: service.description.clone(), + id: service_type.id.clone(), + description: service_type.description.clone(), items, }) } + filtered +} +fn filter_out_poor_gateways( + services: Vec, + gateway_res: Vec, +) -> Vec { 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 - }) + services .into_iter() + .flat_map(|sp| sp.items) .filter(|sp| { gateway_res.iter().any(|gateway| { gateway.gateway_bond.gateway.identity_key == sp.gateway && gateway.performance >= perf_threshold }) }) - .collect(); + .collect() +} - Ok(services_with_good_performance) +async fn fetch_services() -> Result> { + let services_url = get_services_url(); + let services_res = reqwest::get(services_url) + .await? + .json::>() + .await?; + Ok(services_res) +} + +async fn fetch_active_services() -> Result> { + let active_services = reqwest::get(HARBOUR_MASTER_URL) + .await? + .json::>() + .await?; + Ok(active_services) } #[tauri::command] diff --git a/nym-connect/desktop/src/pages/menu/Apps.tsx b/nym-connect/desktop/src/pages/menu/Apps.tsx index ebe31deb44..5de95cc9c3 100644 --- a/nym-connect/desktop/src/pages/menu/Apps.tsx +++ b/nym-connect/desktop/src/pages/menu/Apps.tsx @@ -3,34 +3,31 @@ import { Divider, Typography } from '@mui/material'; import { Box } from '@mui/system'; const appsSchema = { - messagingApps: ['Telegram', 'Keybase'], - wallets: ['Blockstream', 'Electrum'], + messagingApps: ['Matrix', 'Telegram', 'Keybase'], + wallets: ['Monero', 'Blockstream', 'Electrum'], }; export const CompatibleApps = () => ( - + Supported apps Messaging apps - - - + {appsSchema.messagingApps.map((app) => ( {app} ))} + Wallets - - {appsSchema.wallets.map((wallet) => ( diff --git a/nym-connect/mobile/.nvmrc b/nym-connect/mobile/.nvmrc index da2d3988d7..25bf17fc5a 100644 --- a/nym-connect/mobile/.nvmrc +++ b/nym-connect/mobile/.nvmrc @@ -1 +1 @@ -14 \ No newline at end of file +18 \ No newline at end of file diff --git a/nym-wallet/.nvmrc b/nym-wallet/.nvmrc index 19c7bdba7b..25bf17fc5a 100644 --- a/nym-wallet/.nvmrc +++ b/nym-wallet/.nvmrc @@ -1 +1 @@ -16 \ No newline at end of file +18 \ No newline at end of file diff --git a/nym-wallet/CHANGELOG.md b/nym-wallet/CHANGELOG.md index acd33c1bb7..82a2dd9da7 100644 --- a/nym-wallet/CHANGELOG.md +++ b/nym-wallet/CHANGELOG.md @@ -2,11 +2,13 @@ ## [Unreleased] -## [v1.2.5] (2023-06-27) +## [v1.2.5] (2023-07-04) - Wallet - add "Test my node" in the Node Settings and show its results ([#2314]) +- Move client wasm to Wallet directory ([#3606]) [#2314]: https://github.com/nymtech/nym/issues/2314 +[#3606]: https://github.com/nymtech/nym/pull/3606 ## [v1.2.4] (2023-06-06) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 6fa21536f7..248f7a6cd6 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -3104,8 +3104,12 @@ name = "nym-name-service-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", "schemars", "serde", + "thiserror", ] [[package]] diff --git a/nym-wallet/package.json b/nym-wallet/package.json index 4308313fe7..f14d6dd227 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -31,6 +31,7 @@ "@nymproject/mui-theme": "^1.0.0", "@nymproject/react": "^1.0.0", "@nymproject/types": "^1.0.0", + "@nymproject/sdk": "^1.1.8", "@storybook/react": "^6.5.15", "@tauri-apps/api": "^1.2.0", "@tauri-apps/tauri-forage": "^1.0.0-beta.2", @@ -41,7 +42,6 @@ "lodash": "^4.17.21", "notistack": "^2.0.3", "npm-run-all": "^4.1.5", - "nym-client-wasm": "1.1.1", "qrcode.react": "^1.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/nym-wallet/src/components/TestNode/Packets.tsx b/nym-wallet/src/components/TestNode/Packets.tsx index 46ee86bae1..dfbc4d7144 100644 --- a/nym-wallet/src/components/TestNode/Packets.tsx +++ b/nym-wallet/src/components/TestNode/Packets.tsx @@ -1,6 +1,6 @@ import React from 'react'; +import { TestStatus } from '@nymproject/sdk'; import { Divider, Typography } from '@mui/material'; -import { TestStatus } from 'src/pages/bonding/node-settings/node-test/types'; import { ResultsCard, ResultsCardDetail } from './ResultsCard'; export const Packets = ({ diff --git a/nym-wallet/src/components/TestNode/Results.tsx b/nym-wallet/src/components/TestNode/Results.tsx index 0ed14a814c..58856801b3 100644 --- a/nym-wallet/src/components/TestNode/Results.tsx +++ b/nym-wallet/src/components/TestNode/Results.tsx @@ -1,7 +1,6 @@ import React from 'react'; +import { TestStatus } from '@nymproject/sdk'; import { Grid } from '@mui/material'; - -import { TestStatus } from 'src/pages/bonding/node-settings/node-test/types'; import { Packets } from './Packets'; import { NodeScore } from './NodeScore'; import { Overview } from './Overview'; diff --git a/nym-wallet/src/pages/bonding/node-settings/node-test/__mocks__.ts b/nym-wallet/src/pages/bonding/node-settings/node-test/__mocks__.ts deleted file mode 100644 index c7a846d87a..0000000000 --- a/nym-wallet/src/pages/bonding/node-settings/node-test/__mocks__.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { WasmGateway, WasmMixNode, WasmNymTopology } from 'nym-client-wasm'; - -export const createDummyTopology = () => { - const l1Mixnode = new WasmMixNode( - 1, - 'n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47', - '178.79.143.65', - 1789, - '4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz', - '8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG', - 1, - '1.10.0', - ); - - const l2Mixnode = new WasmMixNode( - 2, - 'n1z93z44vf8ssvdhujjvxcj4rd5e3lz0l60wdk70', - '109.74.197.180', - 1789, - '7sVjiMrPYZrDWRujku9QLxgE8noT7NTgBAqizCsu7AoK', - 'GepXwRnKZDd8x2nBWAajGGBVvF3mrpVMQBkgfrGuqRCN', - 2, - '1.10.0', - ); - - const l3Mixnode = new WasmMixNode( - 3, - 'n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77', - '176.58.101.80', - 1789, - 'FoM5Mx9Pxk1g3zEqkS3APgtBeTtTo3M8k7Yu4bV6kK1R', - 'DeYjrDC2AcQRVFshiKnbUo6bRvPyZ33QGYR2DLeFJ9qD', - 3, - '1.10.0', - ); - - const gateway = new WasmGateway( - 'n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts', - '85.159.212.96', - 1789, - 9000, - '336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9', - 'BtYjoWihiuFihGKQypmpSspbhmWDPxzqeTVSd8ciCpWL', - '1.10.1', - ); - - const mixnodes = new Map(); - mixnodes.set(1, [l1Mixnode]); - mixnodes.set(2, [l2Mixnode]); - mixnodes.set(3, [l3Mixnode]); - - const gateways = [gateway]; - - return new WasmNymTopology(mixnodes, gateways); -}; diff --git a/nym-wallet/src/pages/bonding/node-settings/node-test/index.tsx b/nym-wallet/src/pages/bonding/node-settings/node-test/index.tsx index e141d5f69c..7670fe1dcd 100644 --- a/nym-wallet/src/pages/bonding/node-settings/node-test/index.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/node-test/index.tsx @@ -1,19 +1,20 @@ -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { Box, Button } from '@mui/material'; +import { Download } from '@mui/icons-material'; +import { NodeTestResultResponse, NodeTester, TestStatus, createNodeTesterClient } from '@nymproject/sdk'; +import { format } from 'date-fns'; import { AppContext, useBondingContext } from 'src/context'; import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { Results } from 'src/components/TestNode/Results'; import { ErrorModal } from 'src/components/Modals/ErrorModal'; import { PrintResults } from 'src/components/TestNode/PrintResults'; -import { Download } from '@mui/icons-material'; -import { format } from 'date-fns'; -import { NodeTestEvent, NodeTestResult, TestStatus } from './types'; +import { MAINNET_VALIDATOR_URL, QA_VALIDATOR_URL } from 'src/constants'; export const NodeTestPage = () => { - const [nodeTestWorker, setNodeTestWorker] = useState(); + const [nodeTestClient, setNodeTestClient] = useState(); const [error, setError] = useState(); const [isLoading, setIsLoading] = useState(false); - const [results, setResults] = useState(); + const [results, setResults] = useState(); const [printResults, setPrintResults] = useState(false); const [testDate, setTestDate] = useState(); @@ -23,15 +24,6 @@ export const NodeTestPage = () => { const { network } = useContext(AppContext); const { bondedNode } = useBondingContext(); - const loadWorker = () => { - try { - const worker: Worker = new Worker(new URL('./worker.ts', import.meta.url)); - setNodeTestWorker(worker); - } catch (e) { - setError('Error loading worker'); - } - }; - const handleTestTimeout = () => { clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { @@ -43,56 +35,52 @@ export const NodeTestPage = () => { }, 15000); }; - const handleWorkerMessages = (ev: MessageEvent) => { - const eventKind = ev.data.kind; - - if (eventKind === 'Error') { - setError(ev.data.args.message); - testStateRef.current = 'Stopped'; - } - if (eventKind === 'DisplayTesterResults') { - setResults(ev.data.args.result); - testStateRef.current = 'Complete'; - } - setIsLoading(false); - }; - const handleTestNode = async () => { - setError(undefined); - setResults(undefined); - setIsLoading(true); - setTestDate(format(new Date(), 'dd/MM/yyyy HH:mm')); - - if (nodeTestWorker) { + if (nodeTestClient && bondedNode) { + setResults(undefined); + setTestDate(format(new Date(), 'dd/MM/yyyy HH:mm')); + setIsLoading(true); + setError(undefined); testStateRef.current = 'Running'; - nodeTestWorker.postMessage({ - kind: 'TestPacket', - args: { - mixnodeIdentity: bondedNode?.identityKey, - network, - }, - } as NodeTestEvent); handleTestTimeout(); + try { + const result = await nodeTestClient.tester.startTest(bondedNode.identityKey); + setResults(result); + testStateRef.current = 'Complete'; + } catch (e) { + setError('Node test failed, please try again'); + testStateRef.current = 'Stopped'; + console.log(e); + } finally { + setIsLoading(false); + } } }; - useEffect(() => { - loadWorker(); - - return () => { - if (nodeTestWorker) { - nodeTestWorker.terminate(); - } - }; + const loadNodeTestClient = useCallback(async () => { + try { + const nodeTesterId = new Date().toISOString(); // make a new tester id for each session + const validator = network === 'MAINNET' ? MAINNET_VALIDATOR_URL : QA_VALIDATOR_URL; + const client = await createNodeTesterClient(); + await client.tester.init(validator, nodeTesterId); + setNodeTestClient(client); + } catch (e) { + console.log(e); + setError('Failed to load node tester client, please try again'); + } }, []); useEffect(() => { - if (nodeTestWorker) { - nodeTestWorker.addEventListener('message', (e) => handleWorkerMessages(e)); - } + loadNodeTestClient(); - return () => nodeTestWorker?.removeEventListener('message', handleWorkerMessages); - }, [nodeTestWorker]); + return () => { + clearTimeout(timerRef.current); + if (nodeTestClient) { + nodeTestClient.tester.disconnectFromGateway(); + nodeTestClient.terminate(); + } + }; + }, [loadNodeTestClient]); return ( diff --git a/nym-wallet/src/pages/bonding/node-settings/node-test/types.ts b/nym-wallet/src/pages/bonding/node-settings/node-test/types.ts deleted file mode 100644 index a15abf8620..0000000000 --- a/nym-wallet/src/pages/bonding/node-settings/node-test/types.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Network } from 'src/types'; - -export type NodeTestResult = { - score: number; - sentPackets: number; - receivedPackets: number; - receivedAcks: number; - duplicatePackets: number; - duplicateAcks: number; -}; - -type Error = { - kind: 'Error'; - args: { message: string }; -}; - -type WorkerLoaded = { - kind: 'WorkerLoaded'; -}; - -type DisplayTesterResults = { - kind: 'DisplayTesterResults'; - args: { - result: NodeTestResult; - }; -}; - -type TestPacket = { - kind: 'TestPacket'; - args: { - mixnodeIdentity: string; - network: Network; - }; -}; - -export type TestStatus = 'Stopped' | 'Running' | 'Complete'; - -export type NodeTestEvent = Error | DisplayTesterResults | TestPacket | WorkerLoaded; diff --git a/nym-wallet/src/pages/bonding/node-settings/node-test/worker.ts b/nym-wallet/src/pages/bonding/node-settings/node-test/worker.ts deleted file mode 100644 index 5cbd7c2c39..0000000000 --- a/nym-wallet/src/pages/bonding/node-settings/node-test/worker.ts +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2020-2023 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. - -/* eslint-disable no-restricted-globals */ -import { NymNodeTester, set_panic_hook, current_network_topology, NodeTestResult } from 'nym-client-wasm'; -import { Network } from 'src/types'; -import { MAINNET_VALIDATOR_URL, QA_VALIDATOR_URL } from 'src/constants'; -import { NodeTestEvent } from './types'; - -console.log('Initializing worker'); - -const postMessage = (data: NodeTestEvent) => self.postMessage(data); - -postMessage({ - kind: 'WorkerLoaded', -}); - -const printAndDisplayTestResult = (result: NodeTestResult) => { - result.log_details(); - - postMessage({ - kind: 'DisplayTesterResults', - args: { - result: { - score: result.score(), - sentPackets: result.sent_packets, - receivedPackets: result.received_packets, - receivedAcks: result.received_acks, - duplicatePackets: result.duplicate_packets, - duplicateAcks: result.duplicate_acks, - }, - }, - }); -}; - -const buildTester = async (network: Network) => { - const validator = network === 'QA' ? QA_VALIDATOR_URL : MAINNET_VALIDATOR_URL; - const topology = await current_network_topology(validator); - const nodeTester = await new NymNodeTester(topology, network); - - return nodeTester; -}; - -async function testNode() { - self.onmessage = async (event: MessageEvent) => { - const eventKind = event.data.kind; - - switch (eventKind) { - case 'TestPacket': { - const { mixnodeIdentity, network } = event.data.args; - const nodeTester = await buildTester(network); - - try { - console.log(`Testing mixnode identity: ${mixnodeIdentity}, on network: ${network}.`); - const result = await nodeTester.test_node(mixnodeIdentity); - - printAndDisplayTestResult(result); - - await nodeTester.disconnect_from_gateway(); - console.log('Disconnected from gateway'); - } catch (e) { - const errorMessage = e instanceof Error ? e.message : 'Node test error'; - console.log(errorMessage); - - nodeTester.disconnect_from_gateway(); - - postMessage({ - kind: 'Error', - args: { message: errorMessage }, - }); - } - break; - } - default: - return undefined; - } - - return undefined; - }; -} - -async function main() { - // sets up better stack traces in case of in-rust panics - set_panic_hook(); - - // run test on simplified and dedicated tester: - await testNode(); -} - -// Let's get started! -main(); diff --git a/package.json b/package.json index 95b111b2f1..cad6eaf88d 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,7 @@ "nym-connect/**", "explorer", "types", - "clients/validator", - "clients/webassembly/**" + "clients/validator" ], "scripts": { "clean": "lerna run clean", @@ -28,7 +27,9 @@ "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" + "audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run", + "preinstall": "yarn install:copy-placeholders", + "install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm" }, "devDependencies": { "lerna": "^6.6.2", diff --git a/scripts/build/yarn/wasm-placeholder/package.json b/scripts/build/yarn/wasm-placeholder/package.json new file mode 100644 index 0000000000..50f57bc30d --- /dev/null +++ b/scripts/build/yarn/wasm-placeholder/package.json @@ -0,0 +1,5 @@ +{ + "name": "@nymproject/nym-client-wasm", + "version": "1.0.0", + "sideEffects": false +} diff --git a/sdk/typescript/.eslintrc.js b/sdk/typescript/.eslintrc.js index 040935b3bd..52bd4fe9a9 100644 --- a/sdk/typescript/.eslintrc.js +++ b/sdk/typescript/.eslintrc.js @@ -27,7 +27,7 @@ module.exports = { 'plugin:jest/recommended', 'plugin:jest/style', ], - ignorePatterns: ['dist/**/*', 'dist', 'node_modules', '**/wasm/worker.js'], + ignorePatterns: ['dist/**/*', 'dist', 'node_modules', '**/worker.js'], rules: { 'jest/prefer-strict-equal': 'error', 'jest/prefer-to-have-length': 'warn', diff --git a/sdk/typescript/examples/parcel/README.md b/sdk/typescript/examples/chat-app/parcel/README.md similarity index 100% rename from sdk/typescript/examples/parcel/README.md rename to sdk/typescript/examples/chat-app/parcel/README.md diff --git a/sdk/typescript/examples/parcel/package.json b/sdk/typescript/examples/chat-app/parcel/package.json similarity index 100% rename from sdk/typescript/examples/parcel/package.json rename to sdk/typescript/examples/chat-app/parcel/package.json diff --git a/sdk/typescript/examples/parcel/src/dom-utils.ts b/sdk/typescript/examples/chat-app/parcel/src/dom-utils.ts similarity index 100% rename from sdk/typescript/examples/parcel/src/dom-utils.ts rename to sdk/typescript/examples/chat-app/parcel/src/dom-utils.ts diff --git a/sdk/typescript/examples/parcel/src/index.html b/sdk/typescript/examples/chat-app/parcel/src/index.html similarity index 100% rename from sdk/typescript/examples/parcel/src/index.html rename to sdk/typescript/examples/chat-app/parcel/src/index.html diff --git a/sdk/typescript/examples/parcel/src/index.ts b/sdk/typescript/examples/chat-app/parcel/src/index.ts similarity index 100% rename from sdk/typescript/examples/parcel/src/index.ts rename to sdk/typescript/examples/chat-app/parcel/src/index.ts diff --git a/sdk/typescript/examples/chat-app/parcel/tsconfig.json b/sdk/typescript/examples/chat-app/parcel/tsconfig.json new file mode 100644 index 0000000000..86902367db --- /dev/null +++ b/sdk/typescript/examples/chat-app/parcel/tsconfig.json @@ -0,0 +1,9 @@ +{ + "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/.babelrc b/sdk/typescript/examples/chat-app/plain-html/.babelrc similarity index 100% rename from sdk/typescript/examples/plain-html/.babelrc rename to sdk/typescript/examples/chat-app/plain-html/.babelrc diff --git a/sdk/typescript/examples/plain-html/README.md b/sdk/typescript/examples/chat-app/plain-html/README.md similarity index 100% rename from sdk/typescript/examples/plain-html/README.md rename to sdk/typescript/examples/chat-app/plain-html/README.md diff --git a/sdk/typescript/examples/plain-html/jest.config.js b/sdk/typescript/examples/chat-app/plain-html/jest.config.js similarity index 100% rename from sdk/typescript/examples/plain-html/jest.config.js rename to sdk/typescript/examples/chat-app/plain-html/jest.config.js diff --git a/sdk/typescript/examples/plain-html/package.json b/sdk/typescript/examples/chat-app/plain-html/package.json similarity index 100% rename from sdk/typescript/examples/plain-html/package.json rename to sdk/typescript/examples/chat-app/plain-html/package.json diff --git a/sdk/typescript/examples/plain-html/src/dom-utils.ts b/sdk/typescript/examples/chat-app/plain-html/src/dom-utils.ts similarity index 100% rename from sdk/typescript/examples/plain-html/src/dom-utils.ts rename to sdk/typescript/examples/chat-app/plain-html/src/dom-utils.ts diff --git a/sdk/typescript/examples/plain-html/src/index.html b/sdk/typescript/examples/chat-app/plain-html/src/index.html similarity index 100% rename from sdk/typescript/examples/plain-html/src/index.html rename to sdk/typescript/examples/chat-app/plain-html/src/index.html diff --git a/sdk/typescript/examples/plain-html/src/index.ts b/sdk/typescript/examples/chat-app/plain-html/src/index.ts similarity index 100% rename from sdk/typescript/examples/plain-html/src/index.ts rename to sdk/typescript/examples/chat-app/plain-html/src/index.ts diff --git a/sdk/typescript/examples/chat-app/plain-html/tsconfig.json b/sdk/typescript/examples/chat-app/plain-html/tsconfig.json new file mode 100644 index 0000000000..86902367db --- /dev/null +++ b/sdk/typescript/examples/chat-app/plain-html/tsconfig.json @@ -0,0 +1,9 @@ +{ + "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/chat-app/plain-html/tsconfig.prod.json b/sdk/typescript/examples/chat-app/plain-html/tsconfig.prod.json new file mode 100644 index 0000000000..2e8a618c0f --- /dev/null +++ b/sdk/typescript/examples/chat-app/plain-html/tsconfig.prod.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "outDir": "./dist", + "declaration": false + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules", "build", "dist", "**/*.stories.*", "**/*.test.*", "**/*.spec.*"] +} diff --git a/sdk/typescript/examples/plain-html/webpack.common.js b/sdk/typescript/examples/chat-app/plain-html/webpack.common.js similarity index 89% rename from sdk/typescript/examples/plain-html/webpack.common.js rename to sdk/typescript/examples/chat-app/plain-html/webpack.common.js index 3d5f37c912..ae6ef6dc8e 100644 --- a/sdk/typescript/examples/plain-html/webpack.common.js +++ b/sdk/typescript/examples/chat-app/plain-html/webpack.common.js @@ -1,6 +1,6 @@ const path = require('path'); const { mergeWithRules } = require('webpack-merge'); -const { webpackCommon } = require('../.webpack/webpack.base'); +const { webpackCommon } = require('../../.webpack/webpack.base'); module.exports = mergeWithRules({ module: { diff --git a/sdk/typescript/examples/plain-html/webpack.config.js b/sdk/typescript/examples/chat-app/plain-html/webpack.config.js similarity index 100% rename from sdk/typescript/examples/plain-html/webpack.config.js rename to sdk/typescript/examples/chat-app/plain-html/webpack.config.js diff --git a/sdk/typescript/examples/plain-html/webpack.prod.js b/sdk/typescript/examples/chat-app/plain-html/webpack.prod.js similarity index 100% rename from sdk/typescript/examples/plain-html/webpack.prod.js rename to sdk/typescript/examples/chat-app/plain-html/webpack.prod.js diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/.babelrc b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/.babelrc similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/.babelrc rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/.babelrc diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/README.md b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/README.md similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/README.md rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/README.md diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/jest.config.js b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/jest.config.js similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/jest.config.js rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/jest.config.js diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/package.json b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/package.json similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/package.json rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/package.json diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/App.tsx similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/App.tsx diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/ThemeToggle.tsx b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/ThemeToggle.tsx similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/ThemeToggle.tsx rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/ThemeToggle.tsx diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/context/index.tsx b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/context/index.tsx similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/context/index.tsx rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/context/index.tsx diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/context/mixnet.tsx b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/context/mixnet.tsx similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/context/mixnet.tsx rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/context/mixnet.tsx diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/index.html b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/index.html similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/index.html rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/index.html diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/index.tsx b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/index.tsx similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/index.tsx rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/index.tsx diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/tests/App.test.tsx b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/tests/App.test.tsx similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/tests/App.test.tsx rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/tests/App.test.tsx diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/theme/common.ts b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/theme/common.ts similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/theme/common.ts rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/theme/common.ts diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/theme/index.tsx b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/theme/index.tsx similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/theme/index.tsx rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/theme/index.tsx diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/theme/mui-theme.d.ts b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/theme/mui-theme.d.ts similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/theme/mui-theme.d.ts rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/theme/mui-theme.d.ts diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/theme/theme.ts b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/theme/theme.ts similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/theme/theme.ts rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/theme/theme.ts diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/typings/FC.d.ts b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/typings/FC.d.ts similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/typings/FC.d.ts rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/typings/FC.d.ts diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/typings/svg.d.ts b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/typings/svg.d.ts similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/src/typings/svg.d.ts rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/src/typings/svg.d.ts diff --git a/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/tsconfig.json b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/tsconfig.json new file mode 100644 index 0000000000..86902367db --- /dev/null +++ b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/tsconfig.json @@ -0,0 +1,9 @@ +{ + "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/tsconfig.prod.json b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/tsconfig.prod.json similarity index 100% rename from sdk/typescript/examples/plain-html/tsconfig.prod.json rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/tsconfig.prod.json diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/webpack.common.js b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/webpack.common.js similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/webpack.common.js rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/webpack.common.js diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/webpack.config.js b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/webpack.config.js similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/webpack.config.js rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/webpack.config.js diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/webpack.prod.js b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/webpack.prod.js similarity index 100% rename from sdk/typescript/examples/react-webpack-with-theme-example/webpack.prod.js rename to sdk/typescript/examples/chat-app/react-webpack-with-theme-example/webpack.prod.js diff --git a/sdk/typescript/examples/node-tester/parcel/README.md b/sdk/typescript/examples/node-tester/parcel/README.md new file mode 100644 index 0000000000..6669b8f164 --- /dev/null +++ b/sdk/typescript/examples/node-tester/parcel/README.md @@ -0,0 +1,14 @@ +# Nym Node Tester - Parcel bundler + +This is an example of using the Nym Mixnet node tester. + +You can use this example as a seed for a new project. + +## Running the example + +Try out the node tester app by running: + +``` +npm install +npm start +``` diff --git a/sdk/typescript/examples/node-tester/parcel/package.json b/sdk/typescript/examples/node-tester/parcel/package.json new file mode 100644 index 0000000000..27c563a40e --- /dev/null +++ b/sdk/typescript/examples/node-tester/parcel/package.json @@ -0,0 +1,43 @@ +{ + "name": "@nymproject/sdk-example-node-tester-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.8.7", + "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/node-tester/parcel/src/index.html b/sdk/typescript/examples/node-tester/parcel/src/index.html new file mode 100644 index 0000000000..a7bd3057e7 --- /dev/null +++ b/sdk/typescript/examples/node-tester/parcel/src/index.html @@ -0,0 +1,29 @@ + + + + + + + Nym WebAssembly Demo + + + +

+ +

+

+ + + + +

+ +

Test a mixnode by entering the mixnode id above and click the Test button

+ +
+

+

+

+ + + diff --git a/sdk/typescript/examples/node-tester/parcel/src/index.ts b/sdk/typescript/examples/node-tester/parcel/src/index.ts new file mode 100644 index 0000000000..350be6e469 --- /dev/null +++ b/sdk/typescript/examples/node-tester/parcel/src/index.ts @@ -0,0 +1,118 @@ +import { createNodeTesterClient, NodeTester } from '@nymproject/sdk'; + +let nodeTester: NodeTester | null = null; + +/** + * Display messages that have been sent up the websocket. Colours them blue. + * + * @param {string} message + */ +function displayOutput(message: string, color?: string) { + const timestamp = new Date().toISOString().substr(11, 12); + + const sendDiv = document.createElement('div'); + const paragraph = document.createElement('p'); + paragraph.setAttribute('style', `color: ${color || 'blue'}`); + const paragraphContent = document.createTextNode(`${timestamp} >>> ${message}`); + paragraph.appendChild(paragraphContent); + + sendDiv.appendChild(paragraph); + document.getElementById('output')?.appendChild(sendDiv); +} + +/** + * The main entry point + */ +async function main() { + nodeTester = await createNodeTesterClient(); + + // add node tester to the Window globally, so that it can be used from the dev tools console + (window as any).nodeTester = nodeTester; + + if (!nodeTester) { + console.error('Oh no! Could not the node test'); + return; + } + + const nymApiUrl = 'https://validator.nymtech.net/api'; + const nodeTesterId = new Date().toISOString(); // make a new tester id for each session + await nodeTester.tester.init(nymApiUrl, nodeTesterId); + + const mixnodes = await (await fetch(`${nymApiUrl}/v1/mixnodes/active`)).json(); + + const exampleMixnodeIdentityKey = mixnodes[0].bond_information.mix_node.identity_key; + + const testButton: HTMLButtonElement = document.querySelector('#test-button') as HTMLButtonElement; + const reconnectButton: HTMLButtonElement = document.querySelector('#reconnect-button') as HTMLButtonElement; + const disconnectButton: HTMLButtonElement = document.querySelector('#disconnect-button') as HTMLButtonElement; + const terminateButton: HTMLButtonElement = document.querySelector('#terminate-button') as HTMLButtonElement; + + const mixnodeIdInput = document.getElementById('mixnodeId') as HTMLFormElement; + + mixnodeIdInput.value = exampleMixnodeIdentityKey; + + reconnectButton.onclick = async function () { + try { + await nodeTester?.tester.reconnectToGateway(); + } catch (e: any) { + console.error('Error', e); + displayOutput(`ERROR: ${e.message}`, 'red'); + } + }; + + disconnectButton.onclick = async function () { + try { + await nodeTester?.tester.disconnectFromGateway(); + } catch (e: any) { + console.error('Error', e); + displayOutput(`ERROR: ${e.message}`, 'red'); + } + }; + + terminateButton.onclick = async function () { + try { + await nodeTester?.terminate(); + } catch (e: any) { + console.error('Error', e); + displayOutput(`ERROR: ${e.message}`, 'red'); + } + }; + + if (testButton) { + testButton.onclick = async function () { + console.log('clicked'); + + const mixnodeId = mixnodeIdInput.value; + if (!nodeTester) { + displayOutput('ERROR: The node tester is not defined'); + console.error('The node tester is not defined'); + return; + } + if (!mixnodeId) { + displayOutput('ERROR: No mix id specified'); + console.error('No mix id specified'); + return; + } + + if (nodeTester && mixnodeId) { + displayOutput('Starting test...'); + try { + const response = await nodeTester.tester.startTest(mixnodeId); + displayOutput('Done!'); + if (response) { + displayOutput(JSON.stringify(response, null, 2), 'green'); + } + } catch (e: any) { + console.error('Error', e); + displayOutput(`ERROR: ${e.message}`, 'red'); + } + } + }; + } +} + +// wait for the html to load +window.addEventListener('DOMContentLoaded', () => { + // let's do this! + main(); +}); diff --git a/sdk/typescript/examples/node-tester/parcel/tsconfig.json b/sdk/typescript/examples/node-tester/parcel/tsconfig.json new file mode 100644 index 0000000000..5403eafc03 --- /dev/null +++ b/sdk/typescript/examples/node-tester/parcel/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules", "build", "dist"] +} diff --git a/sdk/typescript/examples/node-tester/plain-html/.babelrc b/sdk/typescript/examples/node-tester/plain-html/.babelrc new file mode 100644 index 0000000000..85e1f1ba26 --- /dev/null +++ b/sdk/typescript/examples/node-tester/plain-html/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/env", "@babel/react"] +} \ No newline at end of file diff --git a/sdk/typescript/examples/node-tester/plain-html/README.md b/sdk/typescript/examples/node-tester/plain-html/README.md new file mode 100644 index 0000000000..7a613f68a2 --- /dev/null +++ b/sdk/typescript/examples/node-tester/plain-html/README.md @@ -0,0 +1,14 @@ +# Nym Node Tester - HTML + +This is an example of using the Nym Mixnet node tester. + +You can use this example as a seed for a new project. + +## Running the example + +Try out the node tester app by running: + +``` +npm install +npm start +``` diff --git a/sdk/typescript/examples/node-tester/plain-html/jest.config.js b/sdk/typescript/examples/node-tester/plain-html/jest.config.js new file mode 100644 index 0000000000..e86e13bab9 --- /dev/null +++ b/sdk/typescript/examples/node-tester/plain-html/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; diff --git a/sdk/typescript/examples/node-tester/plain-html/package.json b/sdk/typescript/examples/node-tester/plain-html/package.json new file mode 100644 index 0000000000..b80734f6d4 --- /dev/null +++ b/sdk/typescript/examples/node-tester/plain-html/package.json @@ -0,0 +1,64 @@ +{ + "name": "@nymproject/sdk-example-node-tester-plain-html", + "description": "An example project that uses WASM node tester and plain HTML", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@nymproject/sdk": "1" + }, + "devDependencies": { + "@babel/core": "^7.15.0", + "@babel/plugin-transform-async-to-generator": "^7.14.5", + "@babel/preset-env": "^7.15.0", + "@babel/preset-typescript": "^7.15.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.3.0", + "babel-plugin-root-import": "^5.1.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", + "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.8.7", + "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-merge": "^5.8.0" + }, + "scripts": { + "start": "webpack serve --progress --port 3000", + "build": "webpack build --progress --config webpack.prod.js", + "build:dev": "webpack build --progress", + "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/node-tester/plain-html/src/index.html b/sdk/typescript/examples/node-tester/plain-html/src/index.html new file mode 100644 index 0000000000..ccac6e2365 --- /dev/null +++ b/sdk/typescript/examples/node-tester/plain-html/src/index.html @@ -0,0 +1,29 @@ + + + + + + + Nym WebAssembly Demo + + + +

+ +

+

+ + + + +

+ +

Test a mixnode by entering the mixnode id above and click the Test button

+ +
+

+

+

+ + + diff --git a/sdk/typescript/examples/node-tester/plain-html/src/index.ts b/sdk/typescript/examples/node-tester/plain-html/src/index.ts new file mode 100644 index 0000000000..a2946ba079 --- /dev/null +++ b/sdk/typescript/examples/node-tester/plain-html/src/index.ts @@ -0,0 +1,116 @@ +import { createNodeTesterClient, NodeTester } from '@nymproject/sdk'; + +let nodeTester: NodeTester | null = null; + +/** + * Display messages that have been sent up the websocket. Colours them blue. + * + * @param {string} message + */ +function displayOutput(message: string, color?: string) { + const timestamp = new Date().toISOString().substr(11, 12); + + const sendDiv = document.createElement('div'); + const paragraph = document.createElement('p'); + paragraph.setAttribute('style', `color: ${color || 'blue'}`); + const paragraphContent = document.createTextNode(`${timestamp} >>> ${message}`); + paragraph.appendChild(paragraphContent); + + sendDiv.appendChild(paragraph); + document.getElementById('output')?.appendChild(sendDiv); +} + +/** + * The main entry point + */ +async function main() { + nodeTester = await createNodeTesterClient(); + + // add node tester to the Window globally, so that it can be used from the dev tools console + (window as any).nodeTester = nodeTester; + + if (!nodeTester) { + console.error('Oh no! Could not the node test'); + return; + } + + const nymApiUrl = 'https://validator.nymtech.net/api'; + const nodeTesterId = new Date().toISOString(); // make a new tester id for each session + await nodeTester.tester.init(nymApiUrl, nodeTesterId); + + const mixnodes = await (await fetch(`${nymApiUrl}/v1/mixnodes/active`)).json(); + + const exampleMixnodeIdentityKey = mixnodes[0].bond_information.mix_node.identity_key; + + const sendButton: HTMLButtonElement = document.querySelector('#send-button') as HTMLButtonElement; + const reconnectButton: HTMLButtonElement = document.querySelector('#reconnect-button') as HTMLButtonElement; + const disconnectButton: HTMLButtonElement = document.querySelector('#disconnect-button') as HTMLButtonElement; + const terminateButton: HTMLButtonElement = document.querySelector('#terminate-button') as HTMLButtonElement; + + const mixnodeIdInput = document.getElementById('mixnodeId') as HTMLFormElement; + + mixnodeIdInput.value = exampleMixnodeIdentityKey; + + reconnectButton.onclick = async function () { + try { + await nodeTester?.tester.reconnectToGateway(); + } catch (e: any) { + console.error('Error', e); + displayOutput(`ERROR: ${e.message}`, 'red'); + } + }; + + disconnectButton.onclick = async function () { + try { + await nodeTester?.tester.disconnectFromGateway(); + } catch (e: any) { + console.error('Error', e); + displayOutput(`ERROR: ${e.message}`, 'red'); + } + }; + + terminateButton.onclick = async function () { + try { + await nodeTester?.terminate(); + } catch (e: any) { + console.error('Error', e); + displayOutput(`ERROR: ${e.message}`, 'red'); + } + }; + + if (sendButton) { + sendButton.onclick = async function () { + const mixnodeId = mixnodeIdInput.value; + if (!nodeTester) { + displayOutput('ERROR: The node tester is not defined'); + console.error('The node tester is not defined'); + return; + } + if (!mixnodeId) { + displayOutput('ERROR: No mix id specified'); + console.error('No mix id specified'); + return; + } + + if (nodeTester && mixnodeId) { + displayOutput('Starting test...'); + try { + const response = await nodeTester.tester.startTest(mixnodeId); + displayOutput('Done!'); + if (response) { + displayOutput(JSON.stringify(response, null, 2), 'green'); + } + } catch (e: any) { + console.error('Error', e); + displayOutput(`ERROR: ${e.message}`, 'red'); + } + } + }; + } +} + +// wait for the html to load +window.addEventListener('DOMContentLoaded', () => { + // let's do this! + main(); +}); diff --git a/sdk/typescript/examples/node-tester/plain-html/tsconfig.json b/sdk/typescript/examples/node-tester/plain-html/tsconfig.json new file mode 100644 index 0000000000..5403eafc03 --- /dev/null +++ b/sdk/typescript/examples/node-tester/plain-html/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules", "build", "dist"] +} diff --git a/sdk/typescript/examples/node-tester/plain-html/tsconfig.prod.json b/sdk/typescript/examples/node-tester/plain-html/tsconfig.prod.json new file mode 100644 index 0000000000..cd04ae7aed --- /dev/null +++ b/sdk/typescript/examples/node-tester/plain-html/tsconfig.prod.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "outDir": "./dist", + "declaration": false + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules", "build", "dist", "**/*.stories.*", "**/*.test.*", "**/*.spec.*"] +} diff --git a/sdk/typescript/examples/node-tester/plain-html/webpack.common.js b/sdk/typescript/examples/node-tester/plain-html/webpack.common.js new file mode 100644 index 0000000000..ae6ef6dc8e --- /dev/null +++ b/sdk/typescript/examples/node-tester/plain-html/webpack.common.js @@ -0,0 +1,30 @@ +const path = require('path'); +const { mergeWithRules } = require('webpack-merge'); +const { webpackCommon } = require('../../.webpack/webpack.base'); + +module.exports = mergeWithRules({ + module: { + rules: { + test: 'match', + use: 'replace', + }, + }, +})( + webpackCommon(__dirname, [ + { + inject: true, + filename: 'index.html', + template: path.resolve(__dirname, 'src/index.html'), + chunks: ['index'], + }, + ]), + { + entry: { + index: path.resolve(__dirname, 'src/index.ts'), + }, + output: { + path: path.resolve(__dirname, 'dist'), + publicPath: '/', + }, + }, +); diff --git a/sdk/typescript/examples/node-tester/plain-html/webpack.config.js b/sdk/typescript/examples/node-tester/plain-html/webpack.config.js new file mode 100644 index 0000000000..8c7288de81 --- /dev/null +++ b/sdk/typescript/examples/node-tester/plain-html/webpack.config.js @@ -0,0 +1,67 @@ +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(), + ], + + target: 'web', + + devServer: { + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + historyApiFallback: true, + }, + + // recommended for faster rebuild + optimization: { + runtimeChunk: true, + removeAvailableModules: false, + removeEmptyChunks: false, + splitChunks: false, + }, + + cache: { + type: 'filesystem', + buildDependencies: { + // restart on config change + config: ['./webpack.config.js'], + }, + }, +}); diff --git a/sdk/typescript/examples/node-tester/plain-html/webpack.prod.js b/sdk/typescript/examples/node-tester/plain-html/webpack.prod.js new file mode 100644 index 0000000000..c92239179a --- /dev/null +++ b/sdk/typescript/examples/node-tester/plain-html/webpack.prod.js @@ -0,0 +1,42 @@ +const { mergeWithRules } = require('webpack-merge'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const commonConfig = require('./webpack.common'); + +module.exports = mergeWithRules({ + module: { + rules: { + test: 'match', + use: 'replace', + }, + }, +})(commonConfig, { + mode: 'production', + + // TODO: no source maps, add back + devtool: false, + + module: { + rules: [ + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, 'css-loader'], + }, + ], + }, + optimization: { + minimizer: ['...', new CssMinimizerPlugin()], + splitChunks: { + chunks: 'all', + }, + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: '[name].[contenthash].css', + }), + ], + output: { + pathinfo: false, + filename: '[name].[contenthash].js', + }, +}); diff --git a/sdk/typescript/examples/parcel/tsconfig.json b/sdk/typescript/examples/parcel/tsconfig.json deleted file mode 100644 index b8059e8698..0000000000 --- a/sdk/typescript/examples/parcel/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "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/tsconfig.json b/sdk/typescript/examples/plain-html/tsconfig.json deleted file mode 100644 index b8059e8698..0000000000 --- a/sdk/typescript/examples/plain-html/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "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/react-webpack-with-theme-example/tsconfig.json b/sdk/typescript/examples/react-webpack-with-theme-example/tsconfig.json deleted file mode 100644 index b8059e8698..0000000000 --- a/sdk/typescript/examples/react-webpack-with-theme-example/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "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/react-webpack-with-theme-example/tsconfig.prod.json b/sdk/typescript/examples/react-webpack-with-theme-example/tsconfig.prod.json deleted file mode 100644 index ea260ca2f9..0000000000 --- a/sdk/typescript/examples/react-webpack-with-theme-example/tsconfig.prod.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "jsx": "react-jsx", - "outDir": "./dist", - "declaration": false - }, - "include": [ - "src/**/*.ts", - "src/**/*.tsx" - ], - "exclude": [ - "node_modules", - "build", - "dist", - "**/*.stories.*", - "**/*.test.*", - "**/*.spec.*" - ] -} diff --git a/sdk/typescript/packages/sdk/.gitignore b/sdk/typescript/packages/sdk/.gitignore index f2883f65bd..6166d37c02 100644 --- a/sdk/typescript/packages/sdk/.gitignore +++ b/sdk/typescript/packages/sdk/.gitignore @@ -1 +1,2 @@ src/mixnet/wasm/worker.js +src/mixnet/node-tester/worker.js \ No newline at end of file diff --git a/sdk/typescript/packages/sdk/DEVELOPERS.md b/sdk/typescript/packages/sdk/DEVELOPERS.md index 015271fb96..c8a19f360f 100644 --- a/sdk/typescript/packages/sdk/DEVELOPERS.md +++ b/sdk/typescript/packages/sdk/DEVELOPERS.md @@ -34,6 +34,16 @@ The output bundle will be created in the `dist` directory. 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: +Make sure you have logged into the NPM registry (`npm login`). + +### Scripted + +1. bump version numbers as necessary for SemVer +2. make sure you're in the SDK directory (`cd sdk/typescript/packages/sdk`) +3. run `scripts/publish.sh` + +### Manually + 1. bump version numbers as necessary for SemVer 2. `yarn build` builds the release directory in the root of the repo in `dist/sdk` 3. `cd ../../../../dist/sdk` @@ -45,3 +55,6 @@ Publish the CJS package: Publish the ESM package: - `cd ../esm` - `npm publish --access=public` will publish your changed package to NPM + +4. bump the minor version and add `-rc0` to avoid local packages from using the workspace SDK, e.g. (`1.1.10` -> `1.1.11-rc0`) +5. commit the updated `package.json` to git diff --git a/sdk/typescript/packages/sdk/package.json b/sdk/typescript/packages/sdk/package.json index 7b05d365af..de6d16554d 100644 --- a/sdk/typescript/packages/sdk/package.json +++ b/sdk/typescript/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/sdk", - "version": "1.1.7", + "version": "1.1.9-rc0", "license": "Apache-2.0", "author": "Nym Technologies SA", "type": "module", @@ -26,10 +26,12 @@ "build:only-this": "scripts/build-prod.sh", "prebuild:dev": "yarn build:dependencies", "build:dev": "yarn build:dev:only-this", - "build:dev:only-this": "scripts/build.sh" + "build:dev:only-this": "scripts/build.sh", + "build:local": "run-s build:dependencies:nym-client-wasm build:dev:only-this" }, "dependencies": { "@npmcli/node-gyp": "^3.0.0", + "@nymproject/nym-client-wasm": "1", "comlink": "^4.3.1", "lerna": "^6.6.2", "node-gyp": "^9.3.1" diff --git a/sdk/typescript/packages/sdk/rollup-cjs.config.mjs b/sdk/typescript/packages/sdk/rollup-cjs.config.mjs index fdd081ab49..9f6d102d3d 100644 --- a/sdk/typescript/packages/sdk/rollup-cjs.config.mjs +++ b/sdk/typescript/packages/sdk/rollup-cjs.config.mjs @@ -3,9 +3,7 @@ import resolve from '@rollup/plugin-node-resolve'; import { wasm } from '@rollup/plugin-wasm'; import webWorkerLoader from 'rollup-plugin-web-worker-loader'; -const extensions = [ - '.js', '.jsx', '.ts', '.tsx', -]; +const extensions = ['.js', '.jsx', '.ts', '.tsx']; export default { input: 'src/index.ts', @@ -17,6 +15,9 @@ export default { webWorkerLoader({ targetPlatform: 'browser', inline: true }), resolve({ extensions }), wasm({ maxFileSize: 10000000, targetEnv: 'browser' }), - typescript({ compilerOptions: { outDir: 'dist/cjs', target: 'es5' }, exclude: 'mixnet/wasm/worker.ts', }), + typescript({ + compilerOptions: { outDir: 'dist/cjs', target: 'es5' }, + exclude: ['mixnet/wasm/worker.ts', 'mixnet/node-tester/worker.ts'], + }), ], }; diff --git a/sdk/typescript/packages/sdk/rollup-esm.config.mjs b/sdk/typescript/packages/sdk/rollup-esm.config.mjs index 546188a9c3..3ad4c148fc 100644 --- a/sdk/typescript/packages/sdk/rollup-esm.config.mjs +++ b/sdk/typescript/packages/sdk/rollup-esm.config.mjs @@ -2,9 +2,7 @@ import typescript from '@rollup/plugin-typescript'; import resolve from '@rollup/plugin-node-resolve'; import webWorkerLoader from 'rollup-plugin-web-worker-loader'; -const extensions = [ - '.js', '.jsx', '.ts', '.tsx', -]; +const extensions = ['.js', '.jsx', '.ts', '.tsx']; export default { input: 'src/index.ts', @@ -15,6 +13,9 @@ export default { plugins: [ webWorkerLoader({ targetPlatform: 'browser', inline: true }), resolve({ extensions }), - typescript({ exclude: 'mixnet/wasm/worker.ts', compilerOptions: { outDir: 'dist/esm' } }), + typescript({ + exclude: ['mixnet/wasm/worker.ts', 'mixnet/node-tester/worker.ts'], + compilerOptions: { outDir: 'dist/esm' }, + }), ], }; diff --git a/sdk/typescript/packages/sdk/rollup-node-tester-worker.config.mjs b/sdk/typescript/packages/sdk/rollup-node-tester-worker.config.mjs new file mode 100644 index 0000000000..5f18e7ce33 --- /dev/null +++ b/sdk/typescript/packages/sdk/rollup-node-tester-worker.config.mjs @@ -0,0 +1,26 @@ +import typescript from '@rollup/plugin-typescript'; +import resolve from '@rollup/plugin-node-resolve'; +import { wasm } from '@rollup/plugin-wasm'; +import replace from '@rollup/plugin-replace'; + +const extensions = ['.js', '.jsx', '.ts', '.tsx']; + +export default { + input: 'src/mixnet/node-tester/worker.ts', + output: { + dir: 'dist', + format: 'cjs', + }, + plugins: [ + resolve({ extensions }), + // this is some nasty monkey patching that removes the WASM URL (because it is handled by the `wasm` plugin) + replace({ + values: { "input = new URL('nym_client_wasm_bg.wasm', import.meta.url);": 'input = undefined;' }, + delimiters: ['', ''], + preventAssignment: true, + }), + // force the wasm plugin to embed the wasm bundle - this means no downstream bundlers have to worry about handling it + wasm({ maxFileSize: 10000000, targetEnv: 'browser' }), + typescript({ compilerOptions: { declaration: false, target: 'es5' } }), + ], +}; diff --git a/sdk/typescript/packages/sdk/scripts/build.sh b/sdk/typescript/packages/sdk/scripts/build.sh index 8440a3adda..d2763969e7 100755 --- a/sdk/typescript/packages/sdk/scripts/build.sh +++ b/sdk/typescript/packages/sdk/scripts/build.sh @@ -7,7 +7,7 @@ set -o pipefail rm -rf dist || true #------------------------------------------------------- -# WEB WORKER +# WEB WORKER (WASM client) #------------------------------------------------------- # The web worker needs to be bundled because the WASM bundle needs to be loaded synchronously and all dependencies # must be included in the worker script (because it is not loaded as an ES Module) @@ -19,6 +19,19 @@ rollup -c rollup-worker.config.mjs rm -f src/mixnet/wasm/worker.js mv dist/worker.js src/mixnet/wasm/worker.js +#------------------------------------------------------- +# WEB WORKER (Node tester) +#------------------------------------------------------- +# The web worker needs to be bundled because the WASM bundle needs to be loaded synchronously and all dependencies +# must be included in the worker script (because it is not loaded as an ES Module) + +# build the worker +rollup -c rollup-node-tester-worker.config.mjs + +# move it next to the Typescript `mixnet/node-tester/index.ts` so it can be inlined by rollup +rm -f src/mixnet/node-tester/worker.js +mv dist/worker.js src/mixnet/node-tester/worker.js + #------------------------------------------------------- # ESM #------------------------------------------------------- diff --git a/sdk/typescript/packages/sdk/scripts/publish.sh b/sdk/typescript/packages/sdk/scripts/publish.sh new file mode 100755 index 0000000000..94aa4f689b --- /dev/null +++ b/sdk/typescript/packages/sdk/scripts/publish.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +rm -rf dist || true +rm -rf ../../../../dist || true + +yarn +yarn build +cd ../../../../dist/sdk + +cd cjs +echo "Publishing CommonJS package to NPM.." +npm publish --access=public +cd .. + +cd esm +echo "Publishing ESM package to NPM.." +npm publish --access=public +cd .. diff --git a/sdk/typescript/packages/sdk/src/mixnet/index.ts b/sdk/typescript/packages/sdk/src/mixnet/index.ts index 3c4c661762..2fb66914e6 100644 --- a/sdk/typescript/packages/sdk/src/mixnet/index.ts +++ b/sdk/typescript/packages/sdk/src/mixnet/index.ts @@ -1,2 +1,4 @@ export * from './wasm'; export * from './wasm/types'; +export * from './node-tester'; +export * from './node-tester/types'; diff --git a/sdk/typescript/packages/sdk/src/mixnet/node-tester/constants.ts b/sdk/typescript/packages/sdk/src/mixnet/node-tester/constants.ts new file mode 100644 index 0000000000..55770cd39b --- /dev/null +++ b/sdk/typescript/packages/sdk/src/mixnet/node-tester/constants.ts @@ -0,0 +1,5 @@ +const QA_VALIDATOR_URL = 'https://qa-nym-api.qa.nymte.ch/api'; +const QWERTY_VALIDATOR_URL = 'https://qwerty-validator-api.qa.nymte.ch/api'; +const MAINNET_VALIDATOR_URL = 'https://validator.nymtech.net/api/'; + +export { QA_VALIDATOR_URL, QWERTY_VALIDATOR_URL, MAINNET_VALIDATOR_URL }; diff --git a/sdk/typescript/packages/sdk/src/mixnet/node-tester/index.ts b/sdk/typescript/packages/sdk/src/mixnet/node-tester/index.ts new file mode 100644 index 0000000000..04e52ee579 --- /dev/null +++ b/sdk/typescript/packages/sdk/src/mixnet/node-tester/index.ts @@ -0,0 +1,50 @@ +import InlineWasmWebWorker from 'web-worker:./worker'; +import * as Comlink from 'comlink'; +import { INodeTesterWorkerAsync, NodeTester, NodeTesterEventKinds } from './types'; + +/** + * Client for the Nym node tester. + */ +export const createNodeTesterClient = async (): Promise => { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + const worker = await createWorker(); + + // let comlink handle interop with the web worker + const tester = Comlink.wrap(worker); + + // expose the method to terminate the worker + const terminate = async () => { + worker.terminate(); + }; + + return { tester, terminate }; +}; + +/** + * Async method to create a web worker that runs the Nym node tester client on another thread. It will only return once the worker + * has passed back a `Loaded` event to the calling thread. + * + * @return The instance of the web worker. + */ + +const createWorker = async () => + new Promise((resolve, reject) => { + // rollup will inline the built worker script, so that when the SDK is used in + // other projects, they will not need to mess around trying to bundle it + // however, it will make this SDK bundle bigger because of Base64 inline data + const worker = new InlineWasmWebWorker(); + + worker.addEventListener('error', reject); + worker.addEventListener( + 'message', + (msg) => { + worker.removeEventListener('error', reject); + if (msg.data?.kind === NodeTesterEventKinds.Loaded) { + resolve(worker); + } else { + reject(msg); + } + }, + { once: true }, + ); + }); diff --git a/sdk/typescript/packages/sdk/src/mixnet/node-tester/types.ts b/sdk/typescript/packages/sdk/src/mixnet/node-tester/types.ts new file mode 100644 index 0000000000..a556fec243 --- /dev/null +++ b/sdk/typescript/packages/sdk/src/mixnet/node-tester/types.ts @@ -0,0 +1,65 @@ +export interface INodeTesterWorkerAsync { + init: (validatorUrl: string, nodeTesterId?: string) => Promise; + reconnectToGateway: () => Promise; + disconnectFromGateway: () => Promise; + startTest: (mixnodeIdentityKey: string) => Promise; +} + +export interface INodeTesterWorkerDisposableAsync { + terminate: () => Promise; +} + +export interface NodeTester extends INodeTesterWorkerDisposableAsync { + tester: INodeTesterWorkerAsync; +} + +export enum NodeTesterEventKinds { + Loaded = 'Loaded', + Connected = 'Connected', +} + +export interface NodeTesterLoadedEvent { + kind: NodeTesterEventKinds.Loaded; + args: { + loaded: true; + }; +} + +export type Network = 'QA' | 'SANDBOX' | 'MAINNET'; + +export type NodeTestResultResponse = { + score: number; + sentPackets: number; + receivedPackets: number; + receivedAcks: number; + duplicatePackets: number; + duplicateAcks: number; +}; + +export type Error = { + kind: 'Error'; + args: { message: string }; +}; + +export type WorkerLoaded = { + kind: 'WorkerLoaded'; +}; + +export type DisplayTesterResults = { + kind: 'DisplayTesterResults'; + args: { + result: NodeTestResultResponse; + }; +}; + +export type TestPacket = { + kind: 'TestPacket'; + args: { + mixnodeIdentity: string; + network: Network; + }; +}; + +export type TestStatus = 'Stopped' | 'Running' | 'Complete'; + +export type NodeTestEvent = Error | DisplayTesterResults | TestPacket | WorkerLoaded; diff --git a/sdk/typescript/packages/sdk/src/mixnet/node-tester/worker.ts b/sdk/typescript/packages/sdk/src/mixnet/node-tester/worker.ts new file mode 100644 index 0000000000..ec51afd3ce --- /dev/null +++ b/sdk/typescript/packages/sdk/src/mixnet/node-tester/worker.ts @@ -0,0 +1,105 @@ +// Copyright 2020-2023 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. + +import * as Comlink from 'comlink'; + +// +// Rollup will replace wasmBytes with a function that loads the WASM bundle from a base64 string embedded in the output. +// +// Doing it this way, saves having to support a large variety of bundlers and their quirks. +// +// @ts-ignore +// eslint-disable-next-line import/no-extraneous-dependencies +import wasmBytes from '@nymproject/nym-client-wasm/nym_client_wasm_bg.wasm'; + +/* eslint-disable no-restricted-globals */ +import init, { NymNodeTester, current_network_topology, NodeTestResult } from '@nymproject/nym-client-wasm'; +import type { INodeTesterWorkerAsync, NodeTesterLoadedEvent } from './types'; +import { NodeTesterEventKinds } from './types'; + +/** + * Helper method to send typed messages. + * @param event The strongly typed message to send back to the calling thread. + */ +// eslint-disable-next-line no-restricted-globals +const postMessageWithType = (event: E) => self.postMessage(event); + +console.log('[Nym WASM client] Starting Nym WASM web worker...'); + +const buildTester = async (validatorUrl: string, nodeTesterId?: string): Promise => { + const topology = await current_network_topology(validatorUrl); + return new NymNodeTester(topology, nodeTesterId); +}; + +async function main() { + const importResult = await init(wasmBytes()); + importResult.set_panic_hook(); + + let nodeTester: NymNodeTester | null = null; + + const webWorker: INodeTesterWorkerAsync = { + async init(validatorUrl: string, nodeTesterId?: string) { + nodeTester = await buildTester(validatorUrl, nodeTesterId); + }, + async reconnectToGateway() { + if (!nodeTester) { + throw Error('Please run init first'); + } + await nodeTester.reconnect_to_gateway(); + }, + async disconnectFromGateway() { + if (!nodeTester) { + throw Error('Please run init first'); + } + await nodeTester.disconnect_from_gateway(); + }, + async startTest(mixnodeIdentityKey: string) { + if (!nodeTester) { + throw Error('Please run init first'); + } + console.log(`Testing mixnode with identity key = ${mixnodeIdentityKey}`); + // TODO: fix typing in Rust code + const result = (await nodeTester.test_node(mixnodeIdentityKey)) as NodeTestResult | undefined; + + // return early if there was an error + if (!result) { + return result; + } + + // log the result in the worker so that the packet stats are visible somewhere and extract the score + result.log_details(); + + // eslint-disable-next-line @typescript-eslint/naming-convention + const { duplicate_acks, duplicate_packets, received_acks, received_packets, sent_packets } = result; + + // construct the response to avoid any weird proxy effects + return { + score: result.score(), + sentPackets: sent_packets, + receivedPackets: received_packets, + receivedAcks: received_acks, + duplicatePackets: duplicate_packets, + duplicateAcks: duplicate_acks, + }; + }, + }; + + // start comlink listening for messages and handle them above + Comlink.expose(webWorker); + + // notify any listeners that the web worker has loaded and is ready for testing + postMessageWithType({ kind: NodeTesterEventKinds.Loaded, args: { loaded: true } }); +} + +main(); diff --git a/sdk/typescript/packages/sdk/src/mixnet/wasm/types-from-wasm-pack.ts b/sdk/typescript/packages/sdk/src/mixnet/wasm/types-from-wasm-pack.ts new file mode 100644 index 0000000000..25fbba452c --- /dev/null +++ b/sdk/typescript/packages/sdk/src/mixnet/wasm/types-from-wasm-pack.ts @@ -0,0 +1,336 @@ +/** + */ +export interface TopologyWasm { + free(): void; + /** + * Specifies whether the client should not refresh the network topology after obtaining + * the first valid instance. + * Supersedes `topology_refresh_rate_ms`. + */ + disable_refreshing: boolean; + /** + * The uniform delay every which clients are querying the directory server + * to try to obtain a compatible network topology to send sphinx packets through. + */ + topology_refresh_rate_ms: bigint; + /** + * During topology refresh, test packets are sent through every single possible network + * path. This timeout determines waiting period until it is decided that the packet + * did not reach its destination. + */ + topology_resolution_timeout_ms: bigint; +} +/** + */ +export interface TrafficWasm { + free(): void; + /** + * The parameter of Poisson distribution determining how long, on average, + * sent packet is going to be delayed at any given mix node. + * So for a packet going through three mix nodes, on average, it will take three times this value + * until the packet reaches its destination. + */ + average_packet_delay_ms: bigint; + /** + * Controls whether the main packet stream constantly produces packets according to the predefined + * poisson distribution. + */ + disable_main_poisson_packet_distribution: boolean; + /** + * The parameter of Poisson distribution determining how long, on average, + * it is going to take another 'real traffic stream' message to be sent. + * If no real packets are available and cover traffic is enabled, + * a loop cover message is sent instead in order to preserve the rate. + */ + message_sending_average_delay_ms: bigint; + /** + * Controls whether the sent sphinx packet use the NON-DEFAULT bigger size. + */ + use_extended_packet_size: boolean; + /** + * Controls whether the sent packets should use outfox as opposed to the default sphinx. + */ + use_outfox: boolean; +} +/** + */ +export interface WasmGateway { + free(): void; + /** + */ + clients_port: number; + /** + */ + host: string; + /** + */ + identity_key: string; + /** + */ + mix_port: number; + /** + */ + owner: string; + /** + */ + sphinx_key: string; + /** + */ + version: string; +} + +/** + */ +export interface ReplySurbsWasm { + free(): void; + /** + * Defines the maximum number of reply surbs a remote party is allowed to request from this client at once. + */ + maximum_allowed_reply_surb_request_size: number; + /** + * Defines maximum amount of time given reply key is going to be valid for. + * This is going to be superseded by key rotation once implemented. + */ + maximum_reply_key_age_ms: bigint; + /** + * 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. + */ + maximum_reply_surb_age_ms: bigint; + /** + * 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_ms: bigint; + /** + * Defines the maximum number of reply surbs the client would request. + */ + maximum_reply_surb_request_size: number; + /** + * 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_rerequest_waiting_period_ms: bigint; + /** + * Defines the maximum number of reply surbs the client wants to keep in its storage at any times. + */ + maximum_reply_surb_storage_threshold: number; + /** + * Defines the minimum number of reply surbs the client would request. + */ + minimum_reply_surb_request_size: number; + /** + * Defines the minimum number of reply surbs the client wants to keep in its storage at all times. + * It can only allow to go below that value if its to request additional reply surbs. + */ + minimum_reply_surb_storage_threshold: number; +} + +/** + */ +export interface GatewayConnectionWasm { + free(): void; + /** + * How long we're willing to wait for a response to a message sent to the gateway, + * before giving up on it. + */ + gateway_response_timeout_ms: bigint; +} + +/** + */ +export interface AcknowledgementsWasm { + free(): void; + /** + * Value added to the expected round trip time of an acknowledgement packet before + * it is assumed it was lost and retransmission of the data packet happens. + * In an ideal network with 0 latency, this value would have been 0. + */ + ack_wait_addition_ms: bigint; + /** + * Value multiplied with the expected round trip time of an acknowledgement packet before + * it is assumed it was lost and retransmission of the data packet happens. + * In an ideal network with 0 latency, this value would have been 1. + */ + ack_wait_multiplier: number; + /** + * The parameter of Poisson distribution determining how long, on average, + * sent acknowledgement is going to be delayed at any given mix node. + * So for an ack going through three mix nodes, on average, it will take three times this value + * until the packet reaches its destination. + */ + average_ack_delay_ms: bigint; +} + +/** + */ +export interface CoverTrafficWasm { + free(): void; + /** + * Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic. + * Only applicable if `secondary_packet_size` is enabled. + */ + cover_traffic_primary_size_ratio: number; + /** + * Controls whether the dedicated loop cover traffic stream should be enabled. + * (and sending packets, on average, every [Self::loop_cover_traffic_average_delay]) + */ + disable_loop_cover_traffic_stream: boolean; + /** + * The parameter of Poisson distribution determining how long, on average, + * it is going to take for another loop cover traffic message to be sent. + */ + loop_cover_traffic_average_delay_ms: bigint; +} + +export interface Config { + free(): void; +} + +export interface DebugWasm { + free(): void; + /** + * Defines all configuration options related to acknowledgements, such as delays or wait timeouts. + */ + acknowledgements: AcknowledgementsWasm; + /** + * Defines all configuration options related to cover traffic stream(s). + */ + cover_traffic: CoverTrafficWasm; + /** + * Defines all configuration options related to the gateway connection. + */ + gateway_connection: GatewayConnectionWasm; + /** + * Defines all configuration options related to reply SURBs. + */ + reply_surbs: ReplySurbsWasm; + /** + * Defines all configuration options related topology, such as refresh rates or timeouts. + */ + topology: TopologyWasm; + /** + * Defines all configuration options related to traffic streams. + */ + traffic: TrafficWasm; +} +/** + */ +export interface GatewayEndpointConfig { + free(): void; + /** + * gateway_id specifies ID of the gateway to which the client should send messages. + * If initially omitted, a random gateway will be chosen from the available topology. + */ + gateway_id: string; + /** + * Address of the gateway listener to which all client requests should be sent. + */ + gateway_listener: string; + /** + * Address of the gateway owner to which the client should send messages. + */ + gateway_owner: string; +} +/** + */ +export interface NymClient { + free(): void; + /** + * @returns {string} + */ + self_address(): string; + /** + * The simplest message variant where no additional information is attached. + * You're simply sending your `data` to specified `recipient` without any tagging. + * + * Ends up with `NymMessage::Plain` variant + * @param {Uint8Array} message + * @param {string} recipient + * @returns {Promise} + */ + send_regular_message(message: Uint8Array, recipient: string): Promise; + /** + * Creates a message used for a duplex anonymous communication where the recipient + * will never learn of our true identity. This is achieved by carefully sending `reply_surbs`. + * + * Note that if reply_surbs is set to zero then + * this variant requires the client having sent some reply_surbs in the past + * (and thus the recipient also knowing our sender tag). + * + * Ends up with `NymMessage::Repliable` variant + * @param {Uint8Array} message + * @param {string} recipient + * @param {number} reply_surbs + * @returns {Promise} + */ + send_anonymous_message(message: Uint8Array, recipient: string, reply_surbs: number): Promise; + /** + * Attempt to use our internally received and stored `ReplySurb` to send the message back + * to specified recipient whilst not knowing its full identity (or even gateway). + * + * Ends up with `NymMessage::Reply` variant + * @param {Uint8Array} message + * @param {string} recipient_tag + * @returns {Promise} + */ + send_reply(message: Uint8Array, recipient_tag: string): Promise; +} + +export interface Topology { + free(): void; + /** + * Specifies whether the client should not refresh the network topology after obtaining + * the first valid instance. + * Supersedes `topology_refresh_rate_ms`. + */ + disable_refreshing: boolean; + /** + * The uniform delay every which clients are querying the directory server + * to try to obtain a compatible network topology to send sphinx packets through. + */ + topology_refresh_rate_ms: bigint; + /** + * During topology refresh, test packets are sent through every single possible network + * path. This timeout determines waiting period until it is decided that the packet + * did not reach its destination. + */ + topology_resolution_timeout_ms: bigint; +} + +export interface Traffic { + free(): void; + /** + * The parameter of Poisson distribution determining how long, on average, + * sent packet is going to be delayed at any given mix node. + * So for a packet going through three mix nodes, on average, it will take three times this value + * until the packet reaches its destination. + */ + average_packet_delay_ms: bigint; + /** + * Controls whether the main packet stream constantly produces packets according to the predefined + * poisson distribution. + */ + disable_main_poisson_packet_distribution: boolean; + /** + * The parameter of Poisson distribution determining how long, on average, + * it is going to take another 'real traffic stream' message to be sent. + * If no real packets are available and cover traffic is enabled, + * a loop cover message is sent instead in order to preserve the rate. + */ + message_sending_average_delay_ms: bigint; + /** + * Controls whether the sent sphinx packet use the NON-DEFAULT bigger size. + */ + use_extended_packet_size: boolean; +} + +/** + */ +export interface NymClientBuilder { + free(): void; + /** + * @returns {Promise>} + */ + start_client(): Promise>; +} diff --git a/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts b/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts index 4927f10cfc..1f11384b1a 100644 --- a/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts +++ b/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts @@ -1,174 +1,6 @@ -export interface Acknowledgements { - free(): void; - /** - * Value added to the expected round trip time of an acknowledgement packet before - * it is assumed it was lost and retransmission of the data packet happens. - * In an ideal network with 0 latency, this value would have been 0. - */ - ack_wait_addition_ms: bigint; - /** - * Value multiplied with the expected round trip time of an acknowledgement packet before - * it is assumed it was lost and retransmission of the data packet happens. - * In an ideal network with 0 latency, this value would have been 1. - */ - ack_wait_multiplier: number; - /** - * The parameter of Poisson distribution determining how long, on average, - * sent acknowledgement is going to be delayed at any given mix node. - * So for an ack going through three mix nodes, on average, it will take three times this value - * until the packet reaches its destination. - */ - average_ack_delay_ms: bigint; -} +import type { DebugWasm } from './types-from-wasm-pack'; -export interface CoverTraffic { - free(): void; - /** - * Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic. - * Only applicable if `secondary_packet_size` is enabled. - */ - cover_traffic_primary_size_ratio: number; - /** - * Controls whether the dedicated loop cover traffic stream should be enabled. - * (and sending packets, on average, every [Self::loop_cover_traffic_average_delay]) - */ - disable_loop_cover_traffic_stream: boolean; - /** - * The parameter of Poisson distribution determining how long, on average, - * it is going to take for another loop cover traffic message to be sent. - */ - loop_cover_traffic_average_delay_ms: bigint; -} - -export interface GatewayConnection { - free(): void; - /** - * How long we're willing to wait for a response to a message sent to the gateway, - * before giving up on it. - */ - gateway_response_timeout_ms: bigint; -} - -export interface ReplySurbs { - free(): void; - /** - * Defines the maximum number of reply surbs a remote party is allowed to request from this client at once. - */ - maximum_allowed_reply_surb_request_size: number; - /** - * Defines maximum amount of time given reply key is going to be valid for. - * This is going to be superseded by key rotation once implemented. - */ - maximum_reply_key_age_ms: bigint; - /** - * 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. - */ - maximum_reply_surb_age_ms: bigint; - /** - * 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_ms: bigint; - /** - * Defines the maximum number of reply surbs the client would request. - */ - maximum_reply_surb_request_size: number; - /** - * 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_rerequest_waiting_period_ms: bigint; - /** - * Defines the maximum number of reply surbs the client wants to keep in its storage at any times. - */ - maximum_reply_surb_storage_threshold: number; - /** - * Defines the minimum number of reply surbs the client would request. - */ - minimum_reply_surb_request_size: number; - /** - * Defines the minimum number of reply surbs the client wants to keep in its storage at all times. - * It can only allow to go below that value if its to request additional reply surbs. - */ - minimum_reply_surb_storage_threshold: number; -} - -export interface Debug { - free(): void; - /** - * Defines all configuration options related to acknowledgements, such as delays or wait timeouts. - */ - acknowledgements: Acknowledgements; - /** - * Defines all configuration options related to cover traffic stream(s). - */ - cover_traffic: CoverTraffic; - /** - * Defines all configuration options related to the gateway connection. - */ - gateway_connection: GatewayConnection; - /** - * Defines all configuration options related to reply SURBs. - */ - reply_surbs: ReplySurbs; - /** - * Defines all configuration options related topology, such as refresh rates or timeouts. - */ - topology: Topology; - /** - * Defines all configuration options related to traffic streams. - */ - traffic: Traffic; -} - -export interface Topology { - free(): void; - /** - * Specifies whether the client should not refresh the network topology after obtaining - * the first valid instance. - * Supersedes `topology_refresh_rate_ms`. - */ - disable_refreshing: boolean; - /** - * The uniform delay every which clients are querying the directory server - * to try to obtain a compatible network topology to send sphinx packets through. - */ - topology_refresh_rate_ms: bigint; - /** - * During topology refresh, test packets are sent through every single possible network - * path. This timeout determines waiting period until it is decided that the packet - * did not reach its destination. - */ - topology_resolution_timeout_ms: bigint; -} - -export interface Traffic { - free(): void; - /** - * The parameter of Poisson distribution determining how long, on average, - * sent packet is going to be delayed at any given mix node. - * So for a packet going through three mix nodes, on average, it will take three times this value - * until the packet reaches its destination. - */ - average_packet_delay_ms: bigint; - /** - * Controls whether the main packet stream constantly produces packets according to the predefined - * poisson distribution. - */ - disable_main_poisson_packet_distribution: boolean; - /** - * The parameter of Poisson distribution determining how long, on average, - * it is going to take another 'real traffic stream' message to be sent. - * If no real packets are available and cover traffic is enabled, - * a loop cover message is sent instead in order to preserve the rate. - */ - message_sending_average_delay_ms: bigint; - /** - * Controls whether the sent sphinx packet use the NON-DEFAULT bigger size. - */ - use_extended_packet_size: boolean; -} +export * from './types-from-wasm-pack'; /** * Some common mime types, however, you can always just specify the mime-type as a string @@ -221,7 +53,7 @@ export interface NymClientConfig { /** * Optional. Settings for the WASM client. */ - debug?: Debug; + debug?: DebugWasm; } export interface IWebWorker { diff --git a/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts b/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts index 4dbe9d5154..f1755b5d1f 100644 --- a/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts +++ b/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts @@ -168,43 +168,40 @@ init(wasmBytes()) if (config.gatewayListener) gatewayEndpoint.gateway_listener = config.gatewayListener; // create the client, passing handlers for events - wrapper.init( - new Config(config.clientId, config.nymApiUrl, gatewayEndpoint, config.debug || default_debug()), - async (message) => { - // fire an event with the raw message - postMessageWithType({ - kind: EventKinds.RawMessageReceived, - args: { payload: message }, - }); + wrapper.init(new Config(config.clientId, config.nymApiUrl, config.debug || default_debug()), async (message) => { + // fire an event with the raw message + postMessageWithType({ + kind: EventKinds.RawMessageReceived, + args: { payload: message }, + }); - try { - // try to decode the payload to extract the mime-type, headers and payload body - const decodedPayload = decode_payload(message); - const { payload, headers } = decodedPayload; - const mimeType = decodedPayload.mimeType as MimeTypes; + try { + // try to decode the payload to extract the mime-type, headers and payload body + const decodedPayload = decode_payload(message); + const { payload, headers } = decodedPayload; + const mimeType = decodedPayload.mimeType as MimeTypes; - if (wrapper.getTextMimeTypes().includes(mimeType)) { - const stringMessage = parse_utf8_string(payload); + if (wrapper.getTextMimeTypes().includes(mimeType)) { + const stringMessage = parse_utf8_string(payload); - // the payload is a string type (in the options at creation time, string mime-types are set, or fall back - // to defaults, such as `text/plain`, `application/json`, etc) - postMessageWithType({ - kind: EventKinds.StringMessageReceived, - args: { mimeType, payload: stringMessage, payloadRaw: payload, headers }, - }); - return; - } - - // the payload is a binary type - postMessageWithType({ - kind: EventKinds.BinaryMessageReceived, - args: { mimeType, payload, headers }, + // the payload is a string type (in the options at creation time, string mime-types are set, or fall back + // to defaults, such as `text/plain`, `application/json`, etc) + postMessageWithType({ + kind: EventKinds.StringMessageReceived, + args: { mimeType, payload: stringMessage, payloadRaw: payload, headers }, }); - } catch (e) { - console.error('Failed to parse binary message', e); + return; } - }, - ); + + // the payload is a binary type + postMessageWithType({ + kind: EventKinds.BinaryMessageReceived, + args: { mimeType, payload, headers }, + }); + } catch (e) { + console.error('Failed to parse binary message', e); + } + }); // start the client sending traffic await wrapper.start(); diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 8226b7425a..5b80088b30 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -23,6 +23,7 @@ log = { workspace = true } pretty_env_logger = "0.4.0" publicsuffix = "1.5" # Can't update this until bip updates to support newer idna version rand = "0.7.3" +regex = "1.8.4" reqwest = { version = "0.11.11", features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/service-providers/network-requester/src/allowed_hosts/hosts.rs b/service-providers/network-requester/src/allowed_hosts/hosts.rs index b68159d59e..5c48369d0d 100644 --- a/service-providers/network-requester/src/allowed_hosts/hosts.rs +++ b/service-providers/network-requester/src/allowed_hosts/hosts.rs @@ -128,10 +128,29 @@ impl HostsStore { log::trace!("Loading from storefile: {}", filename.as_ref().display()); let file = File::open(filename)?; let reader = BufReader::new(&file); - Ok(reader + let hosts = reader .lines() - .map(|line| Host::from(line.expect("failed to read input file line!"))) - .collect()) + .filter_map(|line| { + let line = line.expect("failed to read input file line!"); + trim_comment(&line) + }) + .map(Host::from) + .collect(); + dbg!(&hosts); + Ok(hosts) + } +} + +fn trim_comment(line: &str) -> Option { + if let Some(content) = line.split('#').next() { + let trim_content = content.trim().to_string(); + if trim_content.is_empty() { + None + } else { + Some(trim_content) + } + } else { + None } } @@ -145,4 +164,42 @@ mod constructor_tests { let store = HostsStore::new(temp_file); assert_eq!(store.data.domains.len(), 0); } + + #[test] + fn trim_comments() { + let entries = vec![ + "# keybase", + "keybaseapi.com", + "", + "gist.githubusercontent.com", + " ", + "# healthcheck # foo", + "nymtech.net", + "# blockstream green wallet", + "blockstream.info", + "greenaddress.it", + "91.108.56.0/22", + "2001:b28:f23d::/48", + "2001:67c:4e8::/48", + "# nym matrix server", + "# monero desktop - mainnet", + ]; + let filtered_entries: Vec<_> = entries + .iter() + .filter_map(|line| trim_comment(line)) + .collect(); + assert_eq!( + filtered_entries, + vec![ + "keybaseapi.com", + "gist.githubusercontent.com", + "nymtech.net", + "blockstream.info", + "greenaddress.it", + "91.108.56.0/22", + "2001:b28:f23d::/48", + "2001:67c:4e8::/48", + ] + ); + } } diff --git a/service-providers/network-requester/src/allowed_hosts/standard_list.rs b/service-providers/network-requester/src/allowed_hosts/standard_list.rs index 66f529f57f..3b873827e3 100644 --- a/service-providers/network-requester/src/allowed_hosts/standard_list.rs +++ b/service-providers/network-requester/src/allowed_hosts/standard_list.rs @@ -4,6 +4,7 @@ use crate::allowed_hosts::group::HostsGroup; use crate::allowed_hosts::host::Host; use nym_task::TaskClient; +use regex::Regex; use std::sync::Arc; use std::time::Duration; use tokio::sync::{RwLock, RwLockReadGuard}; @@ -11,11 +12,20 @@ use tokio::sync::{RwLock, RwLockReadGuard}; const STANDARD_LIST_URL: &str = "https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt"; +fn remove_comments(text: String) -> String { + if let Ok(regex) = Regex::new(r"#.*\n") { + regex.replace_all(&text, "").into_owned() + } else { + log::warn!("Failed to strip comments from standard allowed list"); + text + } +} + /// Fetch the standard allowed list from nymtech.net pub(crate) async fn fetch() -> Vec { log::info!("Refreshing standard allowed hosts"); - get_standard_allowed_list() - .await + let text = get_standard_allowed_list().await; + remove_comments(text) .split_whitespace() .map(Into::into) .collect() diff --git a/service-providers/network-requester/src/core.rs b/service-providers/network-requester/src/core.rs index 8fa5979c78..b60bc30f41 100644 --- a/service-providers/network-requester/src/core.rs +++ b/service-providers/network-requester/src/core.rs @@ -421,7 +421,9 @@ impl NRServiceProvider { sender_tag: Option, connect_req: Box, ) { - let Some(return_address) = reply::MixnetAddress::new(connect_req.return_address, sender_tag) else { + let Some(return_address) = + reply::MixnetAddress::new(connect_req.return_address, sender_tag) + else { log::warn!( "attempted to start connection with no way of returning data back to the sender" ); diff --git a/ts-packages/mock-nym-api/src/index.ts b/ts-packages/mock-nym-api/src/index.ts index 89ab9ad245..dc5848fd1a 100644 --- a/ts-packages/mock-nym-api/src/index.ts +++ b/ts-packages/mock-nym-api/src/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import express from 'express'; import dotenv from 'dotenv'; import { createProxyMiddleware } from 'http-proxy-middleware'; 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 6f70787807..35bca1929e 100644 --- a/ts-packages/react-webpack-with-theme-example/src/App.tsx +++ b/ts-packages/react-webpack-with-theme-example/src/App.tsx @@ -20,6 +20,7 @@ export const Content: FCWithChildren = () => { const isMounted = useIsMounted(); if (isMounted()) { + // eslint-disable-next-line no-console console.log('Content is mounted'); } diff --git a/yarn.lock b/yarn.lock index 34f8aaa416..2eb29a1a15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + "@adobe/css-tools@^4.0.1": version "4.2.0" resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.2.0.tgz#e1a84fca468f4b337816fcb7f0964beb620ba855" @@ -1203,7 +1208,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== @@ -1808,9 +1813,9 @@ integrity sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g== "@floating-ui/dom@^1.0.0": - version "1.4.2" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.4.2.tgz#eb3a37f7506c4f95ef735967dc3496b5012e11cb" - integrity sha512-VKmvHVatWnewmGGy+7Mdy4cTJX71Pli6v/Wjb5RQBuq5wjUYx+Ef+kRThi8qggZqDgD8CogCpqhRoVp3+yQk+g== + version "1.4.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.4.3.tgz#0854a3297ea03894932381f3ea1403fab3a6e602" + integrity sha512-nB/68NyaQlcdY22L+Fgd1HERQ7UGv7XFN+tPxwrEfQL4nKtAP/jIZnZtpUlXbtV+VEGHh6W/63Gy2C5biWI3sA== dependencies: "@floating-ui/core" "^1.3.1" @@ -2390,6 +2395,20 @@ prop-types "^15.8.1" react-is "^18.2.0" +"@mui/base@5.0.0-beta.5": + version "5.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.5.tgz#b566f3beb1eb2823139eabaf52014cf7be900015" + integrity sha512-vy3TWLQYdGNecTaufR4wDNQFV2WEg6wRPi6BVbx6q1vP3K1mbxIn1+XOqOzfYBXjFHvMx0gZAo2TgWbaqfgvAA== + dependencies: + "@babel/runtime" "^7.22.5" + "@emotion/is-prop-valid" "^1.2.1" + "@mui/types" "^7.2.4" + "@mui/utils" "^5.13.6" + "@popperjs/core" "^2.11.8" + clsx "^1.2.1" + prop-types "^15.8.1" + react-is "^18.2.0" + "@mui/core-downloads-tracker@^5.13.4": version "5.13.4" resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.4.tgz#7e4b491d8081b6d45ae51556d82cb16b31315a19" @@ -2417,16 +2436,16 @@ react-is "^18.2.0" "@mui/material@^5.0.1", "@mui/material@^5.2.2": - version "5.13.5" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.13.5.tgz#c14f14824f3a37ae0c5ebddbc0034956bc6fec30" - integrity sha512-eMay+Ue1OYXOFMQA5Aau7qbAa/kWHLAyi0McsbPTWssCbGehqkF6CIdPsfVGw6tlO+xPee1hUitphHJNL3xpOQ== + version "5.13.6" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.13.6.tgz#caaba1e071e394c415208404ce6964e6c14c16d6" + integrity sha512-/c2ZApeQm2sTYdQXjqEnldaBMBcUEiyu2VRS6bS39ZeNaAcCLBQbYocLR46R+f0S5dgpBzB0T4AsOABPOFYZ5Q== dependencies: - "@babel/runtime" "^7.21.0" - "@mui/base" "5.0.0-beta.4" + "@babel/runtime" "^7.22.5" + "@mui/base" "5.0.0-beta.5" "@mui/core-downloads-tracker" "^5.13.4" - "@mui/system" "^5.13.5" + "@mui/system" "^5.13.6" "@mui/types" "^7.2.4" - "@mui/utils" "^5.13.1" + "@mui/utils" "^5.13.6" "@types/react-transition-group" "^4.4.6" clsx "^1.2.1" csstype "^3.1.2" @@ -2476,16 +2495,16 @@ jss-plugin-vendor-prefixer "^10.10.0" prop-types "^15.8.1" -"@mui/system@>= 5", "@mui/system@^5.0.1", "@mui/system@^5.13.5": - version "5.13.5" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.13.5.tgz#9f67ea0c4f6974713f90b7b94c999fd3f40f8de3" - integrity sha512-n0gzUxoZ2ZHZgnExkh2Htvo9uW2oakofgPRQrDoa/GQOWyRD0NH9MDszBwOb6AAoXZb+OV5TE7I4LeZ/dzgHYA== +"@mui/system@>= 5", "@mui/system@^5.0.1", "@mui/system@^5.13.5", "@mui/system@^5.13.6": + version "5.13.6" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.13.6.tgz#5bf4f84fad0c9ed771458f821e384f61abfa33ca" + integrity sha512-G3Xr28uLqU3DyF6r2LQkHGw/ku4P0AHzlKVe7FGXOPl7X1u+hoe2xxj8Vdiq/69II/mh9OP21i38yBWgWb7WgQ== dependencies: - "@babel/runtime" "^7.21.0" + "@babel/runtime" "^7.22.5" "@mui/private-theming" "^5.13.1" "@mui/styled-engine" "^5.13.2" "@mui/types" "^7.2.4" - "@mui/utils" "^5.13.1" + "@mui/utils" "^5.13.6" clsx "^1.2.1" csstype "^3.1.2" prop-types "^15.8.1" @@ -2495,12 +2514,12 @@ resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.4.tgz#b6fade19323b754c5c6de679a38f068fd50b9328" integrity sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA== -"@mui/utils@^5.10.3", "@mui/utils@^5.13.1", "@mui/utils@^5.7.0": - version "5.13.1" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.13.1.tgz#86199e46014215f95da046a5ec803f4a39c96eee" - integrity sha512-6lXdWwmlUbEU2jUI8blw38Kt+3ly7xkmV9ljzY4Q20WhsJMWiNry9CX8M+TaP/HbtuyR8XKsdMgQW7h7MM3n3A== +"@mui/utils@^5.10.3", "@mui/utils@^5.13.1", "@mui/utils@^5.13.6", "@mui/utils@^5.7.0": + version "5.13.6" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.13.6.tgz#aa29d75de59577585b9f23891b03592d40459ed7" + integrity sha512-ggNlxl5NPSbp+kNcQLmSig6WVB0Id+4gOxhx644987v4fsji+CSXc+MFYLocFB/x4oHtzCUlSzbVHlJfP/fXoQ== dependencies: - "@babel/runtime" "^7.21.0" + "@babel/runtime" "^7.22.5" "@types/prop-types" "^15.7.5" "@types/react-is" "^18.2.0" prop-types "^15.8.1" @@ -2808,9 +2827,6 @@ dependencies: nx "15.9.4" -"@nymproject/nym-client-wasm@file:clients/webassembly/pkg": - version "1.1.1" - "@nymproject/nym-validator-client@^0.18.0": version "0.18.0" resolved "https://registry.yarnpkg.com/@nymproject/nym-validator-client/-/nym-validator-client-0.18.0.tgz#4dd72bafdf6c72b603242f32c0bb9a1f9e475b98" @@ -2822,6 +2838,11 @@ "@cosmjs/stargate" "^0.25.5" axios "^0.21.1" +"@nymproject/sdk@^1.1.8": + version "1.1.8" + resolved "https://registry.yarnpkg.com/@nymproject/sdk/-/sdk-1.1.8.tgz#384ee824971dee413755b14634c3d60fb22eff9a" + integrity sha512-nAyem2wqTfc6l9e6Y7JR2V1teDfwjxMHpRjxg90FMkUWl/JP7Ww752gDsSq3nvObLt+cvxjWMHy1mswvwQfZyQ== + "@octokit/auth-token@^3.0.0": version "3.0.4" resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-3.0.4.tgz#70e941ba742bdd2b49bdb7393e821dea8520a3db" @@ -3036,10 +3057,10 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== -"@remix-run/router@1.6.3": - version "1.6.3" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.6.3.tgz#8205baf6e17ef93be35bf62c37d2d594e9be0dad" - integrity sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q== +"@remix-run/router@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.0.tgz#550a8d5760b78efc5d60f62b5829b0f74c1fde81" + integrity sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw== "@rollup/plugin-commonjs@^24.0.1": version "24.1.0" @@ -3107,9 +3128,9 @@ resolve "^1.22.1" "@rollup/plugin-typescript@^11.0.0": - version "11.1.1" - resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-11.1.1.tgz#258663a7aa6b51390dd39ae6e5502f2c4b2807cb" - integrity sha512-Ioir+x5Bejv72Lx2Zbz3/qGg7tvGbxQZALCLoJaGrkNXak/19+vKgKYJYM3i/fJxvsb23I9FuFQ8CUBEfsmBRg== + version "11.1.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-11.1.2.tgz#09eb5690a650bb0334bf84125bce9abd296442e4" + integrity sha512-0ghSOCMcA7fl1JM+0gYRf+Q/HWyg+zg7/gDSc+fRLmlJWcW5K1I+CLRzaRhXf4Y3DRyPnnDo4M2ktw+a6JcDEg== dependencies: "@rollup/pluginutils" "^5.0.1" resolve "^1.22.1" @@ -3142,79 +3163,79 @@ resolved "https://registry.yarnpkg.com/@sapphire/utilities/-/utilities-3.12.0.tgz#3ee8330338aa79a7e0cab04d4d0eec9ed69fc9bf" integrity sha512-y1JP4FdPBkZjJGJcb5Zx0ok64mtpGvz0TLOC49HTwYsfBQL8tsTct93yoYdEEMWSZFmOF1ZvV8L6jIJ1CW8bJw== -"@sentry-internal/tracing@7.56.0": - version "7.56.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.56.0.tgz#ba709258f2f0f3d8a36f9740403088b39212b843" - integrity sha512-OKI4Pz/O13gng8hT9rNc+gRV3+P7nnk1HnHlV8fgaQydS6DsRxoDL1sHa42tZGbh7K9jqNAP3TC6VjBOsr2tXA== +"@sentry-internal/tracing@7.57.0": + version "7.57.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.57.0.tgz#cb761931b635f8f24c84be0eecfacb8516b20551" + integrity sha512-tpViyDd8AhQGYYhI94xi2aaDopXOPfL2Apwrtb3qirWkomIQ2K86W1mPmkce+B0cFOnW2Dxv/ZTFKz6ghjK75A== dependencies: - "@sentry/core" "7.56.0" - "@sentry/types" "7.56.0" - "@sentry/utils" "7.56.0" - tslib "^1.9.3" + "@sentry/core" "7.57.0" + "@sentry/types" "7.57.0" + "@sentry/utils" "7.57.0" + tslib "^2.4.1 || ^1.9.3" -"@sentry/browser@7.56.0": - version "7.56.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.56.0.tgz#6bf3ff21bc2e9b66a72ea0c7dcc3572fdeb3bd8f" - integrity sha512-qpyyw+NM/psbNAYMlTCCdWwxHHcogppEQ+Q40jGE4sKP2VRIjjyteJkUcaEMoGpbJXx9puzTWbpzqlQ8r15Now== +"@sentry/browser@7.57.0": + version "7.57.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.57.0.tgz#6e724c9eac680dba99ced0fdf81be8d1e3b3bceb" + integrity sha512-E0HaYYlaqHFiIRZXxcvOO8Odvlt+TR1vFFXzqUWXPOvDRxURglTOCQ3EN/u6bxtAGJ6y/Zc2obgihTtypuel/w== dependencies: - "@sentry-internal/tracing" "7.56.0" - "@sentry/core" "7.56.0" - "@sentry/replay" "7.56.0" - "@sentry/types" "7.56.0" - "@sentry/utils" "7.56.0" - tslib "^1.9.3" + "@sentry-internal/tracing" "7.57.0" + "@sentry/core" "7.57.0" + "@sentry/replay" "7.57.0" + "@sentry/types" "7.57.0" + "@sentry/utils" "7.57.0" + tslib "^2.4.1 || ^1.9.3" -"@sentry/core@7.56.0": - version "7.56.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.56.0.tgz#f4253e0d61f55444180a63e5278b62e57303f7cf" - integrity sha512-Nuyyfh09Yz27kPo74fXHlrdmZeK6zrlJVtxQ6LkwuoaTBcNcesNXVaOtr6gjvUGUmsfriVPP3Jero5LXufV7GQ== +"@sentry/core@7.57.0": + version "7.57.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.57.0.tgz#65093d739c04f320a54395a21be955fcbe326acb" + integrity sha512-l014NudPH0vQlzybtXajPxYFfs9w762NoarjObC3gu76D1jzBBFzhdRelkGpDbSLNTIsKhEDDRpgAjBWJ9icfw== dependencies: - "@sentry/types" "7.56.0" - "@sentry/utils" "7.56.0" - tslib "^1.9.3" + "@sentry/types" "7.57.0" + "@sentry/utils" "7.57.0" + tslib "^2.4.1 || ^1.9.3" "@sentry/integrations@^7.54.0": - version "7.56.0" - resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.56.0.tgz#6fe812454fbccf00810f65667eb393b3bf298b92" - integrity sha512-0d/E/R3MW+5bQ9wcHPD0h/B2KFOpUt+wQgN1kNk7atY12auDzHKuGCjPP87D/xVyRoma3ErFqZCRqwtvCj1cfQ== + version "7.57.0" + resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.57.0.tgz#298085b3a2fe862cc70bc7f2143aa0fbc617322c" + integrity sha512-C3WZo5AGI2L0dj+mIjeZpdAwDEG2nDYvZbTzq5J9hVoHFdP3t7fOWBHSPkSFVtTdMaJrv+82aKnUefVCeAjxGg== dependencies: - "@sentry/types" "7.56.0" - "@sentry/utils" "7.56.0" + "@sentry/types" "7.57.0" + "@sentry/utils" "7.57.0" localforage "^1.8.1" - tslib "^1.9.3" + tslib "^2.4.1 || ^1.9.3" "@sentry/react@^7.54.0": - version "7.56.0" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.56.0.tgz#7e2e9363a76c7d67854bdb179142a2f7f910d476" - integrity sha512-dRnkZwspef5aEHV/eiLS/mhomFCXItylU8s78fzAn5QMTDGHmu5Pnhl5dxh/zbPUcdXqFA6GwJVucV4gzsNEJw== + version "7.57.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.57.0.tgz#cf91f0115bcd2a8306d6c8a39d8e8b53d4b21814" + integrity sha512-XGNTjIoCG3naSmCU8qObd+y+CqAB6NQkGWOp2yyBwp2inyKF2ehJvDh6bIQloBYq2TmOJDa4NfXdMrkilxaLFQ== dependencies: - "@sentry/browser" "7.56.0" - "@sentry/types" "7.56.0" - "@sentry/utils" "7.56.0" + "@sentry/browser" "7.57.0" + "@sentry/types" "7.57.0" + "@sentry/utils" "7.57.0" hoist-non-react-statics "^3.3.2" - tslib "^1.9.3" + tslib "^2.4.1 || ^1.9.3" -"@sentry/replay@7.56.0": - version "7.56.0" - resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.56.0.tgz#8a49dcb45e9ea83bf905cec0d9b42fed4b8085bd" - integrity sha512-bvjiJK1+SM/paLapuL+nEJ8CIF1bJqi0nnFyxUIi2L5L6yb2uMwfyT3IQ+kz0cXJsLdb3HN4WMphVjyiU9pFdg== +"@sentry/replay@7.57.0": + version "7.57.0" + resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.57.0.tgz#c8f7eae7b7edc9d32c3d2955b337f3b3c76dff39" + integrity sha512-pN4ryNS3J5EYbkXvR+O/+hseAJha7XDl8mPFtK0OGTHG10JzCi4tQJazblHQdpb5QBaMMPCeZ+isyfoQLDNXnw== dependencies: - "@sentry/core" "7.56.0" - "@sentry/types" "7.56.0" - "@sentry/utils" "7.56.0" + "@sentry/core" "7.57.0" + "@sentry/types" "7.57.0" + "@sentry/utils" "7.57.0" -"@sentry/types@7.56.0": - version "7.56.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.56.0.tgz#9042a099cf9e8816d081886d24b88082a5d9f87a" - integrity sha512-5WjhVOQm75ItOytOx2jTx+5yw8/qJ316+g1Di8dS9+kgIi1zniqdMcX00C2yYe3FMUgFB49PegCUYulm9Evapw== +"@sentry/types@7.57.0": + version "7.57.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.57.0.tgz#4fdb80cbd49ba034dd8d9be0c0005a016d5db3ce" + integrity sha512-D7ifoUfxuVCUyktIr5Gc+jXUbtcUMmfHdTtTbf1XCZHua5mJceK9wtl3YCg3eq/HK2Ppd52BKnTzEcS5ZKQM+w== -"@sentry/utils@7.56.0": - version "7.56.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.56.0.tgz#e60e4935d17b2197584abf6ce61b522ad055352c" - integrity sha512-wgeX7bufxc//TjjSIE+gCMm8hVId7Jzvc+f441bYrWnNZBuzPIDW2BummCcPrKzSYe5GeYZDTZGV8YZGMLGBjw== +"@sentry/utils@7.57.0": + version "7.57.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.57.0.tgz#8253c6fcf35138b4c424234b8da1596e11b98ad8" + integrity sha512-YXrkMCiNklqkXctn4mKYkrzNCf/dfVcRUQrkXjeBC+PHXbcpPyaJgInNvztR7Skl8lE3JPGPN4v5XhLxK1bUUg== dependencies: - "@sentry/types" "7.56.0" - tslib "^1.9.3" + "@sentry/types" "7.57.0" + tslib "^2.4.1 || ^1.9.3" "@sigstore/protobuf-specs@^0.1.0": version "0.1.0" @@ -3620,14 +3641,14 @@ qs "^6.10.0" telejson "^6.0.8" -"@storybook/channel-postmessage@7.0.23": - version "7.0.23" - resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-7.0.23.tgz#eff248def859a0106cb45d535eb13111b8abb2df" - integrity sha512-SfXTV55Z9U5rN1OuyR56s+PUpav3b4SgXtP67bnNsrv7dkKhBwr0DUUJogIRnjmY0Loy/hLvJ23kfmKXPWC4vQ== +"@storybook/channel-postmessage@7.0.24": + version "7.0.24" + resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-7.0.24.tgz#789bd121cf64e306737ec0f3b096ea0ac6f47bf5" + integrity sha512-QLtLXjEeTEwBN/7pB888mBaykmRU9Jy2BitvZuLJWyHHygTYm3vYZOaGR37DT+q/6Ob5GaZ0tURZmCSNDe8IIA== dependencies: - "@storybook/channels" "7.0.23" - "@storybook/client-logger" "7.0.23" - "@storybook/core-events" "7.0.23" + "@storybook/channels" "7.0.24" + "@storybook/client-logger" "7.0.24" + "@storybook/core-events" "7.0.24" "@storybook/global" "^5.0.0" qs "^6.10.0" telejson "^7.0.3" @@ -3652,10 +3673,10 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/channels@7.0.23": - version "7.0.23" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-7.0.23.tgz#4c57f5f43289ca21b6a713ff9c399c8abb6c868a" - integrity sha512-cCxR3Z84YQjsVMPgFTI+kDVNOlgXSDakwjkNFBznU+s2qhGW5eZt2g9YRDeVDQ6AjR4j4RrGhwddRq4lQZF2pg== +"@storybook/channels@7.0.24": + version "7.0.24" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-7.0.24.tgz#fcfe7a1a5599265506f07fe81d03b0585303fc15" + integrity sha512-NZVLwMhtzy6cZrNRjshFvMAD9mQTmJDNwhohodSkM/YFCDVFhmxQk9tgizVGh9MwY3CYGJ1SI96RUejGosb49Q== "@storybook/client-api@6.5.16": version "6.5.16" @@ -3691,10 +3712,10 @@ core-js "^3.8.2" global "^4.4.0" -"@storybook/client-logger@7.0.23", "@storybook/client-logger@^6.4.0 || >=6.5.0-0": - version "7.0.23" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-7.0.23.tgz#5db5d144d1948fd8c856195ee6a699c002046557" - integrity sha512-L287SRO8EaYOxTpryV7N/1WCL5I1IFs5Naiq3FpybhguUP7F3Si7KWvVdFmSW06K9jNj2IEQ/8zBRM8ra4ttyg== +"@storybook/client-logger@7.0.24", "@storybook/client-logger@^6.4.0 || >=6.5.0-0": + version "7.0.24" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-7.0.24.tgz#7be57ce1f90e65c6e9da7a88ed9ca7c5ce3c6536" + integrity sha512-4zRTb+QQ1hWaRqad/UufZNRfi2d/cf5a40My72Ct97VwjhJFE6aQ3K+hl1Xt6hh8dncDL2JK3cgziw6ElqjT0w== dependencies: "@storybook/global" "^5.0.0" @@ -3801,10 +3822,10 @@ dependencies: core-js "^3.8.2" -"@storybook/core-events@7.0.23": - version "7.0.23" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.0.23.tgz#99d36712dba8eae9e1585298dd9b97385ae13ad5" - integrity sha512-Hdt18p/qbgJc+1wY2dGcdjmlsuNXWsoLTaXrjInuvr1U0kmKmKs0VMaB0cFubnUgCmB3YQWTGnVr3q8iz9iB7g== +"@storybook/core-events@7.0.24": + version "7.0.24" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.0.24.tgz#3fa4b2b3af64a86525b4cf07f49a7410125ef810" + integrity sha512-xkf/rihCkhqMeh5EA8lVp90/mzbb2gcg6I3oeFWw2hognVcTnPXg6llhWdU4Spqd0cals7GEFmQugIILCmH8GA== "@storybook/core-server@6.5.16": version "6.5.16" @@ -3936,15 +3957,15 @@ global "^4.4.0" "@storybook/instrumenter@^6.4.0 || >=6.5.0-0": - version "7.0.23" - resolved "https://registry.yarnpkg.com/@storybook/instrumenter/-/instrumenter-7.0.23.tgz#0f7efd95cb2ec4698fc57cc012045cec42b7ef6b" - integrity sha512-CeV2se64XxccD4L6XFI3cFfEz3/Lcbrvb+T3bZZzGOXO18zH5tN3jXVfAONrz/mU69jL9mbo96hFl51UDtwcAg== + version "7.0.24" + resolved "https://registry.yarnpkg.com/@storybook/instrumenter/-/instrumenter-7.0.24.tgz#fbc81250f25b0d5f3da3a3aa9dad48cdc82642f9" + integrity sha512-XQ4Whq0rqW9eFMTtRpLxcl6bCf+KO3UZYcm+H63EDn9TstDyopmqv1fDg2tmJOpqLo143F8qLVC89rI7M/lO6w== dependencies: - "@storybook/channels" "7.0.23" - "@storybook/client-logger" "7.0.23" - "@storybook/core-events" "7.0.23" + "@storybook/channels" "7.0.24" + "@storybook/client-logger" "7.0.24" + "@storybook/core-events" "7.0.24" "@storybook/global" "^5.0.0" - "@storybook/preview-api" "7.0.23" + "@storybook/preview-api" "7.0.24" "@storybook/manager-webpack4@6.5.16": version "6.5.16" @@ -4060,18 +4081,18 @@ dependencies: core-js "^3.8.2" -"@storybook/preview-api@7.0.23": - version "7.0.23" - resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-7.0.23.tgz#6f4d98a54c7367341e086a1818866e2e1e97faea" - integrity sha512-kXhDX6gVjQu4Lx4SnCW5Yt5W/TbQofp9SL0paB1ywsJ15xSAPU5KVILe9OWAOba2YUnk7sHux/xDX/gH5RCpVw== +"@storybook/preview-api@7.0.24": + version "7.0.24" + resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-7.0.24.tgz#93634c060aa43bb10fb233f28ae83b7d74b98aad" + integrity sha512-psycU07tuB5nyJvfAJiDN/9e8cjOdJ+5lrCSYC3vPzH86LxADDIN0/8xFb1CaQWcXZsADEFJGpHKWbRhjym5ew== dependencies: - "@storybook/channel-postmessage" "7.0.23" - "@storybook/channels" "7.0.23" - "@storybook/client-logger" "7.0.23" - "@storybook/core-events" "7.0.23" + "@storybook/channel-postmessage" "7.0.24" + "@storybook/channels" "7.0.24" + "@storybook/client-logger" "7.0.24" + "@storybook/core-events" "7.0.24" "@storybook/csf" "^0.1.0" "@storybook/global" "^5.0.0" - "@storybook/types" "7.0.23" + "@storybook/types" "7.0.24" "@types/qs" "^6.9.5" dequal "^2.0.2" lodash "^4.17.21" @@ -4252,15 +4273,15 @@ memoizerific "^1.11.3" regenerator-runtime "^0.13.7" -"@storybook/types@7.0.23": - version "7.0.23" - resolved "https://registry.yarnpkg.com/@storybook/types/-/types-7.0.23.tgz#08ae3223e97ed1baa9aa715745a05ae83311425c" - integrity sha512-ziszL3OfhTT5PHE7kiQjWWx3Lw3qro8eLX+56dXDNgmft5LS66yEANcaA7OzxLnEgdyWSxJqgrVo6r0JwHp2Eg== +"@storybook/types@7.0.24": + version "7.0.24" + resolved "https://registry.yarnpkg.com/@storybook/types/-/types-7.0.24.tgz#22b2f6658f9a091ff584e1a0643ee1ea9a54ed39" + integrity sha512-SZh/XBHP1TT5bmEk0W52nT0v6fUnYwmZVls3da5noutdgOAiwL7TANtl41XrNjG+UDr8x0OE3PVVJi+LhwUaNA== dependencies: - "@storybook/channels" "7.0.23" + "@storybook/channels" "7.0.24" "@types/babel__core" "^7.0.0" "@types/express" "^4.7.0" - file-system-cache "^2.0.0" + file-system-cache "2.3.0" "@storybook/ui@6.5.16": version "6.5.16" @@ -5001,9 +5022,9 @@ form-data "^3.0.0" "@types/node@*", "@types/node@>=13.7.0": - version "20.3.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" - integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== + version "20.3.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.2.tgz#fa6a90f2600e052a03c18b8cb3fd83dd4e599898" + integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw== "@types/node@^13.7.0": version "13.13.52" @@ -5011,9 +5032,9 @@ integrity sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ== "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0", "@types/node@^16.7.13": - version "16.18.36" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.36.tgz#0db5d7efc4760d36d0d1d22c85d1a53accd5dc27" - integrity sha512-8egDX8dE50XyXWH6C6PRCNkTP106DuUrvdrednFouDSmCi7IOvrqr0frznfZaHifHH/3aq/7a7v9N4wdXMqhBQ== + version "16.18.37" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.37.tgz#a1f8728e4dc30163deb41e9b7aba65d0c2d4eda1" + integrity sha512-ql+4dw4PlPFBP495k8JzUX/oMNRI2Ei4PrMHgj8oT4VhGlYUzF4EYr0qk2fW+XBVGIrq8Zzk13m4cvyXZuv4pA== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -5113,9 +5134,9 @@ "@types/react" "*" "@types/react@*", "@types/react@^18.0.26": - version "18.2.13" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.13.tgz#a98c09bde8b18f80021935b11d2d29ef5f4dcb2f" - integrity sha512-vJ+zElvi/Zn9cVXB5slX2xL8PZodPCwPRDpittQdw43JR2AJ5k3vKdgJJyneV/cYgIbLQUwXa9JVDvUZXGba+Q== + version "18.2.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.14.tgz#fa7a6fecf1ce35ca94e74874f70c56ce88f7a127" + integrity sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -5300,14 +5321,14 @@ integrity sha512-3NoqvZC2W5gAC5DZbTpCeJ251vGQmgcWIHQJGq2J240HY6ErQ9aWKkwfoKJlHLx+A83WPNTZ9+3cd2ILxbvr1w== "@typescript-eslint/eslint-plugin@^5.13.0", "@typescript-eslint/eslint-plugin@^5.7.0": - version "5.60.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.0.tgz#2f4bea6a3718bed2ba52905358d0f45cd3620d31" - integrity sha512-78B+anHLF1TI8Jn/cD0Q00TBYdMgjdOn980JfAVa9yw5sop8nyTfVOQAv6LWywkOGLclDBtv5z3oxN4w7jxyNg== + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz#81382d6ecb92b8dda70e91f9035611cb2fecd1c3" + integrity sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.60.0" - "@typescript-eslint/type-utils" "5.60.0" - "@typescript-eslint/utils" "5.60.0" + "@typescript-eslint/scope-manager" "5.60.1" + "@typescript-eslint/type-utils" "5.60.1" + "@typescript-eslint/utils" "5.60.1" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -5316,78 +5337,78 @@ tsutils "^3.21.0" "@typescript-eslint/experimental-utils@^5.3.0": - version "5.60.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.60.0.tgz#48ffa47238592397c3d857fe1403eed3b1d5e604" - integrity sha512-ovid3u7CNBrr0Ct35LUPkNYH4e+z4Kc6dPfSG99oMmH9SfoEoefq09uSnJI4mUb/UM7a/peVM03G+MzLxrD16g== + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.60.1.tgz#d783bb63b9183541019a945eda6a9d96b096d985" + integrity sha512-TXUdLxv2t8181nh5yLXl/Gr/zKj1ZofQ7m+ZdmG2+El0TYOHCvlZfc35D4nturemC3RUnf3KmLuFp3bVBjkG5w== dependencies: - "@typescript-eslint/utils" "5.60.0" + "@typescript-eslint/utils" "5.60.1" "@typescript-eslint/parser@^5.13.0", "@typescript-eslint/parser@^5.7.0": - version "5.60.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.60.0.tgz#08f4daf5fc6548784513524f4f2f359cebb4068a" - integrity sha512-jBONcBsDJ9UoTWrARkRRCgDz6wUggmH5RpQVlt7BimSwaTkTjwypGzKORXbR4/2Hqjk9hgwlon2rVQAjWNpkyQ== + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.60.1.tgz#0f2f58209c0862a73e3d5a56099abfdfa21d0fd3" + integrity sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q== dependencies: - "@typescript-eslint/scope-manager" "5.60.0" - "@typescript-eslint/types" "5.60.0" - "@typescript-eslint/typescript-estree" "5.60.0" + "@typescript-eslint/scope-manager" "5.60.1" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/typescript-estree" "5.60.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.60.0": - version "5.60.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.60.0.tgz#ae511967b4bd84f1d5e179bb2c82857334941c1c" - integrity sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ== +"@typescript-eslint/scope-manager@5.60.1": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz#35abdb47f500c68c08f2f2b4f22c7c79472854bb" + integrity sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ== dependencies: - "@typescript-eslint/types" "5.60.0" - "@typescript-eslint/visitor-keys" "5.60.0" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/visitor-keys" "5.60.1" -"@typescript-eslint/type-utils@5.60.0": - version "5.60.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.60.0.tgz#69b09087eb12d7513d5b07747e7d47f5533aa228" - integrity sha512-X7NsRQddORMYRFH7FWo6sA9Y/zbJ8s1x1RIAtnlj6YprbToTiQnM6vxcMu7iYhdunmoC0rUWlca13D5DVHkK2g== +"@typescript-eslint/type-utils@5.60.1": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz#17770540e98d65ab4730c7aac618003f702893f4" + integrity sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A== dependencies: - "@typescript-eslint/typescript-estree" "5.60.0" - "@typescript-eslint/utils" "5.60.0" + "@typescript-eslint/typescript-estree" "5.60.1" + "@typescript-eslint/utils" "5.60.1" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.60.0": - version "5.60.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.60.0.tgz#3179962b28b4790de70e2344465ec97582ce2558" - integrity sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA== +"@typescript-eslint/types@5.60.1": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.60.1.tgz#a17473910f6b8d388ea83c9d7051af89c4eb7561" + integrity sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg== -"@typescript-eslint/typescript-estree@5.60.0": - version "5.60.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.0.tgz#4ddf1a81d32a850de66642d9b3ad1e3254fb1600" - integrity sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ== +"@typescript-eslint/typescript-estree@5.60.1": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz#8c71824b7165b64d5ebd7aa42968899525959834" + integrity sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw== dependencies: - "@typescript-eslint/types" "5.60.0" - "@typescript-eslint/visitor-keys" "5.60.0" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/visitor-keys" "5.60.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.60.0", "@typescript-eslint/utils@^5.10.0": - version "5.60.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.60.0.tgz#4667c5aece82f9d4f24a667602f0f300864b554c" - integrity sha512-ba51uMqDtfLQ5+xHtwlO84vkdjrqNzOnqrnwbMHMRY8Tqeme8C2Q8Fc7LajfGR+e3/4LoYiWXUM6BpIIbHJ4hQ== +"@typescript-eslint/utils@5.60.1", "@typescript-eslint/utils@^5.10.0": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.60.1.tgz#6861ebedbefba1ac85482d2bdef6f2ff1eb65b80" + integrity sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.60.0" - "@typescript-eslint/types" "5.60.0" - "@typescript-eslint/typescript-estree" "5.60.0" + "@typescript-eslint/scope-manager" "5.60.1" + "@typescript-eslint/types" "5.60.1" + "@typescript-eslint/typescript-estree" "5.60.1" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.60.0": - version "5.60.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.0.tgz#b48b29da3f5f31dd1656281727004589d2722a66" - integrity sha512-wm9Uz71SbCyhUKgcaPRauBdTegUyY/ZWl8gLwD/i/ybJqscrrdVSFImpvUz16BLPChIeKBK5Fa9s6KDQjsjyWw== +"@typescript-eslint/visitor-keys@5.60.1": + version "5.60.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz#19a877358bf96318ec35d90bfe6bd1445cce9434" + integrity sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw== dependencies: - "@typescript-eslint/types" "5.60.0" + "@typescript-eslint/types" "5.60.1" eslint-visitor-keys "^3.3.0" "@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": @@ -5689,9 +5710,9 @@ integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== "@yarnpkg/parsers@^3.0.0-rc.18": - version "3.0.0-rc.46" - resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz#03f8363111efc0ea670e53b0282cd3ef62de4e01" - integrity sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q== + version "3.0.0-rc.47" + resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.47.tgz#2b67a2f5098cafcd0a1ce7f2e9174794d639046e" + integrity sha512-ri/QnMiMobEgRUBF1fPcxH1ICa9jsG8y4tDNRAuk6zPdCPkSAzACKaXjmDiAZFiwKxK4hTVj0E8nklgWFeTLng== dependencies: js-yaml "^3.10.0" tslib "^2.4.0" @@ -6045,9 +6066,9 @@ aria-query@5.1.3: deep-equal "^2.0.5" aria-query@^5.0.0, aria-query@^5.1.3: - version "5.2.1" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.2.1.tgz#bc285d9d654d1df121bcd0c134880d415ca67c15" - integrity sha512-7uFg4b+lETFgdaJyETnILsXgnnzVnkHcgRbwbPwevm5x/LmUlt3MjczMRe1zg824iBgXZNRPTBftNYyRSKLp2g== + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== dependencies: dequal "^2.0.3" @@ -6099,7 +6120,7 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== -array-includes@^3.0.3, array-includes@^3.1.5, array-includes@^3.1.6: +array-includes@^3.0.3, array-includes@^3.1.6: version "3.1.6" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== @@ -6122,11 +6143,6 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array-union@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975" - integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw== - array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -7086,9 +7102,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001503: - version "1.0.30001507" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001507.tgz#fae53f6286e7564783eadea9b447819410a59534" - integrity sha512-SFpUDoSLCaE5XYL2jfqe9ova/pbQHEmbheDf5r4diNwbAgR3qxM9NQtfsiSscjqoya5K7kFcHPUQ+VsUkIJR4A== + version "1.0.30001509" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001509.tgz#2b7ad5265392d6d2de25cd8776d1ab3899570d14" + integrity sha512-2uDDk+TRiTX5hMcUYT/7CSyzMZxjfGu0vAUjS2g0LSD8UoXOv0LtpH4LxGMemsiPq6LCVIUjNwVM0erkOkGCDA== capture-exit@^2.0.0: version "2.0.0" @@ -7807,18 +7823,6 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== -copy-webpack-plugin@^10.2.4: - version "10.2.4" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe" - integrity sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg== - 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" - core-js-compat@^3.30.1, core-js-compat@^3.30.2, core-js-compat@^3.8.1: version "3.31.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.0.tgz#4030847c0766cc0e803dcdfb30055d7ef2064bf1" @@ -8913,9 +8917,9 @@ ejs@^3.1.7: jake "^10.8.5" electron-to-chromium@^1.4.431: - version "1.4.439" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.439.tgz#d0748e8128c18f92354d3412dfb2104c3fb90e8f" - integrity sha512-BHpErPSNhb9FB25+OwQP6mCAf3ZXfGbmuvc4LzBNVJwpCcXQJm++LerimocYRG9FRxUVRKZqaB7d0+pImSTPSg== + version "1.4.445" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.445.tgz#058d2c5f3a2981ab1a37440f5a5e42d15672aa6d" + integrity sha512-++DB+9VK8SBJwC+X1zlMfJ1tMA3F0ipi39GdEp+x3cV2TyBihqAgad8cNMWtLDEkbH39nlDQP7PfGrDr3Dr7HA== elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" @@ -9018,9 +9022,9 @@ env-paths@^2.2.0: integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== envinfo@^7.7.3, envinfo@^7.7.4: - version "7.9.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.9.0.tgz#47594a13081be0d9be6e513534e8c58dbb26c7a1" - integrity sha512-RODB4txU+xImYDemN5DqaKC0CHk05XSVkOX4pq0hK26Qx+1LChkuOyUDlGEjYb3ACr0n9qBhFjg37hQuJvpkRQ== + version "7.10.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.10.0.tgz#55146e3909cc5fe63c22da63fb15b05aeac35b13" + integrity sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw== err-code@^2.0.2: version "2.0.3" @@ -9854,7 +9858,7 @@ fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" -fast-glob@^3.2.7, fast-glob@^3.2.9: +fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== @@ -9972,6 +9976,14 @@ file-loader@^6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" +file-system-cache@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-2.3.0.tgz#201feaf4c8cd97b9d0d608e96861bb6005f46fe6" + integrity sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ== + dependencies: + fs-extra "11.1.1" + ramda "0.29.0" + file-system-cache@^1.0.5: version "1.1.0" resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.1.0.tgz#984de17b976b75a77a27e08d6828137c1aa80fa1" @@ -9980,14 +9992,6 @@ file-system-cache@^1.0.5: fs-extra "^10.1.0" ramda "^0.28.0" -file-system-cache@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-2.3.0.tgz#201feaf4c8cd97b9d0d608e96861bb6005f46fe6" - integrity sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ== - dependencies: - fs-extra "11.1.1" - ramda "0.29.0" - file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -10552,7 +10556,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^6.0.1, glob-parent@^6.0.2: +glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== @@ -10601,15 +10605,15 @@ glob@7.2.0: path-is-absolute "^1.0.0" glob@^10.2.2: - version "10.3.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.0.tgz#763d02a894f3cdfc521b10bbbbc8e0309e750cce" - integrity sha512-AQ1/SB9HH0yCx1jXAT4vmCbTOPe5RQ+kCurjbel5xSCGhebumUv+GJZfa1rEqor3XIViqwSEmlkZCQD43RWrBg== + version "10.3.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.1.tgz#9789cb1b994515bedb811a6deca735b5c37d2bf4" + integrity sha512-9BKYcEeIs7QwlCYs+Y3GBvqAMISufUS0i2ELd11zpZjxI5V9iyRj0HgzB5/cLf2NY4vcYBTYzJ7GIui7j/4DOw== dependencies: foreground-child "^3.1.0" jackspeak "^2.0.3" minimatch "^9.0.1" minipass "^5.0.0 || ^6.0.2" - path-scurry "^1.7.0" + path-scurry "^1.10.0" glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" @@ -10683,18 +10687,6 @@ globby@11.1.0, globby@^11.0.1, globby@^11.0.2, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -globby@^12.0.2: - version "12.2.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-12.2.0.tgz#2ab8046b4fba4ff6eede835b29f678f90e3d3c22" - integrity sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA== - 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" - globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -10974,11 +10966,6 @@ he@1.2.0, he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -hello-wasm-pack@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/hello-wasm-pack/-/hello-wasm-pack-0.1.0.tgz#482a2e3371828056ac35f5b5fec76c0b99dcd530" - integrity sha512-3hx0GDkDLf/a9ThCMV2qG4mwza8N/MCtm8aeFFc/cdBCL2zMJ1kW1wjNl7xPqD1lz8Yl5+uhnc/cpui4dLwz/w== - hex-rgb@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/hex-rgb/-/hex-rgb-4.3.0.tgz#af5e974e83bb2fefe44d55182b004ec818c07776" @@ -11051,9 +11038,9 @@ html-encoding-sniffer@^2.0.1: whatwg-encoding "^1.0.5" html-entities@^2.1.0, html-entities@^2.3.2: - version "2.3.6" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.6.tgz#966391d58e5737c77bca4025e31721b496ab7454" - integrity sha512-9o0+dcpIw2/HxkNuYKxSJUF/MMRZQECK4GnF+oQOmJ83yCVHTWgCH5aOXxK5bozNRmM8wtgryjHD3uloPBDEGw== + version "2.4.0" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061" + integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ== html-escaper@^2.0.0: version "2.0.2" @@ -11303,7 +11290,7 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.0.4, ignore@^5.1.9, ignore@^5.2.0: +ignore@^5.0.4, ignore@^5.2.0: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== @@ -12916,12 +12903,14 @@ jss@10.10.0, jss@^10.10.0: tiny-warning "^1.0.2" "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea" - integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw== + version "3.3.4" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.4.tgz#b896535fed5b867650acce5a9bd4135ffc7b3bf9" + integrity sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw== dependencies: - array-includes "^3.1.5" - object.assign "^4.1.3" + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" junk@^3.1.0: version "3.1.0" @@ -13384,10 +13373,10 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== -lru-cache@^9.1.1: - version "9.1.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.2.tgz#255fdbc14b75589d6d0e73644ca167a8db506835" - integrity sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ== +"lru-cache@^9.1.1 || ^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61" + integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw== lunr@^2.3.9: version "2.3.9" @@ -13724,9 +13713,9 @@ mem@^8.1.1: mimic-fn "^3.1.0" memfs@^3.1.2, memfs@^3.2.2, memfs@^3.4.1, memfs@^3.4.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.5.3.tgz#d9b40fe4f8d5788c5f895bda804cd0d9eeee9f3b" - integrity sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw== + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== dependencies: fs-monkey "^1.0.4" @@ -14248,9 +14237,9 @@ minimatch@^8.0.2: brace-expansion "^2.0.1" minimatch@^9.0.0, minimatch@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253" - integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w== + version "9.0.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.2.tgz#397e387fff22f6795844d00badc903a3d5de7057" + integrity sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg== dependencies: brace-expansion "^2.0.1" @@ -15104,7 +15093,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.2, object.assign@^4.1.3, object.assign@^4.1.4: +object.assign@^4.1.2, object.assign@^4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== @@ -15233,16 +15222,16 @@ optionator@^0.8.1: word-wrap "~1.2.3" optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" 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" ora@^5.4.1: version "5.4.1" @@ -15682,12 +15671,12 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.6.1, path-scurry@^1.7.0: - version "1.9.2" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.9.2.tgz#90f9d296ac5e37e608028e28a447b11d385b3f63" - integrity sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg== +path-scurry@^1.10.0, path-scurry@^1.6.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.0.tgz#0ffbd4c1f7de9600f98a1405507d9f9acb438ab3" + integrity sha512-tZFEaRQbMLjwrsmidsGJ6wDMv0iazJWk6SfIKnY4Xru8auXgmJkOBa5DUbYFcFD2Rzk2+KDlIiF0GVXNCbgC7g== dependencies: - lru-cache "^9.1.1" + lru-cache "^9.1.1 || ^10.0.0" minipass "^5.0.0 || ^6.0.2" path-to-regexp@0.1.7: @@ -16659,9 +16648,9 @@ react-google-charts@^3.0.15: react-load-script "^0.0.6" react-hook-form@^7.14.2: - version "7.45.0" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.45.0.tgz#df2bbc8cee598855a63ba446e0bb06f7c8120ccf" - integrity sha512-AbHeZ4ad+0dEIknSW9dBgIwcvRDfZ1O97sgj75WaMdOX0eg8TBiUf9wxzVkIjZbk76BBIE9lmFOzyD4PN80ZQg== + version "7.45.1" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.45.1.tgz#e352c7f4dbc7540f0756abbb4dcfd1122fecc9bb" + integrity sha512-6dWoFJwycbuFfw/iKMcl+RdAOAOHDiF11KWYhNDRN/OkUt+Di5qsZHwA0OwsVnu9y135gkHpTw9DJA+WzCeR9w== react-identicons@^1.2.5: version "1.2.5" @@ -16751,19 +16740,19 @@ react-resize-detector@^8.0.4: lodash "^4.17.21" react-router-dom@6, react-router-dom@^6.7.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.13.0.tgz#6651f456bb2af42ef14f6880123b1f575539e81f" - integrity sha512-6Nqoqd7fgwxxVGdbiMHTpDHCYPq62d7Wk1Of7B82vH7ZPwwsRaIa22zRZKPPg413R5REVNiyuQPKDG1bubcOFA== + version "6.14.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.0.tgz#7ce6c3f73549e4d40216ba150253d3bf30812b33" + integrity sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ== dependencies: - "@remix-run/router" "1.6.3" - react-router "6.13.0" + "@remix-run/router" "1.7.0" + react-router "6.14.0" -react-router@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.13.0.tgz#7e4427a271dae0cafbdb88c56ccbd9b1434ee93f" - integrity sha512-Si6KnfEnJw7gUQkNa70dlpI1bul46FuSxX5t5WwlUBxE25DAz2BjVkwaK8Y2s242bQrZPXCpmwLPtIO5pv4tXg== +react-router@6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.0.tgz#1c3e8e922d934d43a253fd862c72c82167c0a7f1" + integrity sha512-OD+vkrcGbvlwkspUFDgMzsu1RXwdjNh83YgG/28lBnDzgslhCgxIqoExLlxsfTpIygp7fc+Hd3esloNwzkm2xA== dependencies: - "@remix-run/router" "1.6.3" + "@remix-run/router" "1.7.0" react-simple-maps@^2.3.0: version "2.3.0" @@ -16784,9 +16773,9 @@ react-smooth@^2.0.2: react-transition-group "2.9.0" react-tooltip@*: - version "5.15.0" - resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.15.0.tgz#54a1fdbd43b5ce90733c7495bc8b575e9c88f58e" - integrity sha512-OiH870zteP4T5B/J7cUGz37aY8C0Cny/i1qcLXevL+wZlW09mfjdcQ6FKP9OMMsRlszRwieOqAPT9l8rWz+BtA== + version "5.16.1" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.16.1.tgz#dafb42170c50d93eaa06e26581ba8e6f7ceb2c48" + integrity sha512-z3IKJppX4llW8wQLQB3o5qUutVul79HVXI+Ncjf/5FMHbniCYPnxS1oCLpOhOEG9lV2QZRrQe9+l/pLEPOrrXA== dependencies: "@floating-ui/dom" "^1.0.0" classnames "^2.3.0" @@ -16965,9 +16954,9 @@ readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stre util-deprecate "^1.0.1" readable-stream@^4.1.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.4.0.tgz#55ce132d60a988c460d75c631e9ccf6a7229b468" - integrity sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg== + version "4.4.1" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.4.1.tgz#fa0f0878c3bc0c12b6a82e4e58c5dc160e1faaa2" + integrity sha512-llAHX9QC25bz5RPIoTeJxPaA/hgryaldValRhVZ2fK9bzbmFiscpz8fw6iBTvJfAk1w4FC1KXQme/nO7fbKyKg== dependencies: abort-controller "^3.0.0" buffer "^6.0.3" @@ -17416,9 +17405,9 @@ rollup-plugin-web-worker-loader@^1.6.1: integrity sha512-4QywQSz1NXFHKdyiou16mH3ijpcfLtLGOrAqvAqu1Gx+P8+zj+3gwC2BSL/VW1d+LW4nIHC8F7d7OXhs9UdR2A== rollup@^3.17.2, rollup@^3.2.1, rollup@^3.9.1: - version "3.25.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.25.1.tgz#9fff79d22ff1a904b2b595a2fb9bc3793cb987d8" - integrity sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ== + version "3.25.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.25.3.tgz#f9a8986f0f244bcfde2208da91ba46b8fd252551" + integrity sha512-ZT279hx8gszBj9uy5FfhoG4bZx8c+0A1sbqtr7Q3KNWIizpTdDEPZbV2xcbvHsnFp4MavCQYZyzApJ+virB8Yw== optionalDependencies: fsevents "~2.3.2" @@ -17903,11 +17892,6 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== - slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" @@ -18728,9 +18712,9 @@ terser@^4.1.2, terser@^4.6.3: source-map-support "~0.5.12" terser@^5.10.0, terser@^5.15.1, terser@^5.16.8, terser@^5.3.4: - version "5.18.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.18.1.tgz#6d8642508ae9fb7b48768e48f16d675c89a78460" - integrity sha512-j1n0Ao919h/Ai5r43VAnfV/7azUYW43GPxK7qSATzrsERfW7+y2QW9Cp9ufnRF5CQUWbnLSo7UJokSWCqg4tsQ== + version "5.18.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.18.2.tgz#ff3072a0faf21ffd38f99acc9a0ddf7b5f07b948" + integrity sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -18976,9 +18960,9 @@ ts-jest@^27.0.5: yargs-parser "20.x" ts-loader@^9.4.2: - version "9.4.3" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.4.3.tgz#55cfa7c28dd82a2de968ae45c3adb75fb888b27e" - integrity sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA== + version "9.4.4" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.4.4.tgz#6ceaf4d58dcc6979f84125335904920884b7cee4" + integrity sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w== dependencies: chalk "^4.1.0" enhanced-resolve "^5.0.0" @@ -19070,10 +19054,10 @@ tslib@^1.8.1, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0: - version "2.5.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" - integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, "tslib@^2.4.1 || ^1.9.3": + version "2.6.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" + integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== tsutils@^3.21.0: version "3.21.0" @@ -19878,7 +19862,7 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== -webpack-cli@^4.8.0, webpack-cli@^4.9.2: +webpack-cli@^4.8.0: version "4.10.0" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w== @@ -19930,7 +19914,7 @@ webpack-dev-middleware@^5.3.1: range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@^4.5.0, webpack-dev-server@^4.7.4: +webpack-dev-server@^4.5.0: version "4.15.1" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz#8944b29c12760b3a45bdaa70799b17cb91b03df7" integrity sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA== @@ -20057,10 +20041,10 @@ webpack@4: watchpack "^1.7.4" webpack-sources "^1.4.1" -"webpack@>=4.43.0 <6.0.0", webpack@^5.70.0, webpack@^5.75.0, webpack@^5.9.0: - version "5.88.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.0.tgz#a07aa2f8e7a64a8f1cec0c6c2e180e3cb34440c8" - integrity sha512-O3jDhG5e44qIBSi/P6KpcCcH7HD+nYIHVBhdWFxcLOcIGN8zGo5nqF3BjyNCxIh4p1vFdNnreZv2h2KkoAw3lw== +"webpack@>=4.43.0 <6.0.0", webpack@^5.75.0, webpack@^5.9.0: + version "5.88.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.1.tgz#21eba01e81bd5edff1968aea726e2fbfd557d3f8" + integrity sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.0" @@ -20203,7 +20187,7 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== -word-wrap@^1.2.3, word-wrap@~1.2.3: +word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==