Compare commits

..

94 Commits

Author SHA1 Message Date
serinko 7686c60333 DOC: smoosh-faq.md - quotation syntax fix 2023-09-19 16:46:43 +00:00
serinko 98c03ffa56 DOC: smoosh-faq.md - quotation syntax fix 2023-09-19 16:41:44 +00:00
serinko 0a3a31dedc DOC: smoosh-faq.md - quotation syntax fix 2023-09-19 16:40:27 +00:00
serinko 79ae9b69ef DOC: smoosh-faq.md - quotation syntax fix 2023-09-19 16:39:17 +00:00
serinko 549b58311d Merge pull request #3906 from nymtech/patch/documentation/syntax-fix
Fixing bugs in mdbook build errors (links, admonish, path) -> CI/CD runs without a problem.
2023-09-19 15:52:57 +00:00
serinko 279b494a60 corrected surbs line 2023-09-19 12:54:36 +02:00
serinko 3be0a6cf65 fixed broken code path 2023-09-19 10:51:03 +02:00
serinko 9b8add1daa fixed broken links 2023-09-19 10:36:25 +02:00
Mark Sinclair 2193378d42 Merge pull request #3905 from nymtech/bugfix/ci-cd-docs
Docs: make shell scripts exit on errors so that CI jobs fail on build errors
2023-09-19 09:28:33 +01:00
Mark Sinclair c57263e91b Docs: make shell scripts exit on errors so that CI jobs fail on build errors 2023-09-19 09:22:33 +01:00
Mark Sinclair 685f26792f Typescript SDK Nextra Docs (#3880)
* Remove pnpm-lock.yaml

* Add initial documentation

* updating packages and disconnecting on mixFetch when unmount

* handle the mixFetch error

* remove the mixfetch disconexion

* using now rc5 version

* Update overview

* Update installation page

* wip startong

* Copy edits and improving some of the formatting and styling

* Improve naming

* Add CosmosKit example

* Linting

* Update next.js

* Remove lock file

* More CosmosKit docs

* wip

* cleaninig a bit

* quick fix for wallet error

* wip wallet ui

* wip wallet ui

* more wallet ui

* fixing key error

* wip

* Example code

* Add custom style for code blocks to limit their height and scroll

* Change bg on darkmode

* Add styling to darkmode

* Reorg CSS - tbc

* Move example code

* Ledger support in Cosmos Kit - wip

* Change default app to Typescript

* Remove static export

* Tidy up wallet UI

* Set theme colour by hue

* Force dark mode theme

* some wallet ui

* Style buttons sidebar

* Sidebar colors

* Links styling

* Style callouts

* Add styling to button, chips, progress motion component

* Style agenda

* adding loaders

* wallet loaders

* traffic styles

* Fix colours

* Add links to methods

* Add execute code block

* Add traffic codeblock

* Add mixfetch codeblock

* Add Cosmokit codeblock

* Update info on getting started

* fixing build

* Fix build error

* Fix theme

* Fix filenames on examples

* Add copy to CosmosKit example

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: Gala <calero.vg@gmail.com>
Co-authored-by: Lorexia <alexia.lorenza.martinel@protonmail.com>
2023-09-18 19:46:32 +01:00
Jon Häggblad 109659152d Remove leftover .faq files 2023-09-18 17:11:18 +02:00
benedetta davico 73a75f7aef Master (#3888)
* removed old wallet address flag again

* Add updates to community list projects

* Update cd-docs.yml

* Update cd-docs.yml

* [hotfix]: don't assign invalid fields when crossing the JS boundary (#3805)

* [hotfix]: don't assign invalid fields when crossing the JS boundary

* eslint

* changelog update and version bump

* changed last vers. checkout to master

* corrected path of config

* make binaries executable

* docs: typescript.md - changing variables

* docs: rust.md - changing variables

* docs: vesting-contract.md - changing variables

* docs: mixnet-contract.md - changing variables

* docs: all variables changed

* operators: all variables finished

* dev-portal: mixnet-integration.md - variable changed

* dev-portal: faq.md - variable changed

* dev-portal: moredo.md up to date w NC default

* dev-portal: telegram.md - added banner & minor fix

* dev-portal: matrix.md - added banner

* PR finished - ready for review and merge

* removed all instances of platform_release_version var

* removed all instances of platform_release_version var

* changed version bumper script: removed platform_release_version references

* changed comment

* updating changelog and bumping versions

* Docs: new post-processing for books so that assets stay relative

This commit has the same content as https://github.com/nymtech/nym/pull/3842

* Docs: add prod deploy settings

* fixed ChangeMixCostParams event deserialization (#3873)

* Merge pull request #3892 from nymtech/feature/operators/smoosh-faq

Create smoosh FAQ section & re-organize operators/faq accordingly

* corrected faq dir path

* added integrations-faq page

---------

Co-authored-by: mfahampshire <maxhampshire@pm.me>
Co-authored-by: Lorexia <alexia.lorenza.martinel@protonmail.com>
Co-authored-by: Tommy Verrall <tommy@nymtech.net>
Co-authored-by: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com>
Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com>
Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: serinko <97586125+serinko@users.noreply.github.com>
Co-authored-by: mx <33262279+mfahampshire@users.noreply.github.com>
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: Tommy Verrall <tommyvez@protonmail.com>
2023-09-18 17:06:10 +02:00
benedettadavico 06956efa3f Deleting faq.md on develop 2023-09-18 17:04:58 +02:00
serinko f130ff73ad Merge pull request #3900 from nymtech/patch/dev-portal/integrations-faq-fix
integrations added to developers/faq section
2023-09-18 13:40:45 +00:00
serinko ddaabdc856 Merge pull request #3892 from nymtech/feature/operators/smoosh-faq
Create smoosh FAQ section & re-organize operators/faq accordingly
2023-09-18 13:38:54 +00:00
serinko 65de9d146d removed redundant .faq dir 2023-09-18 12:20:19 +02:00
serinko ec3aa7d8eb time frame in intro & ETA delete 2023-09-18 11:58:00 +02:00
serinko 805e1b8759 add NymAPI codebase link 2023-09-18 11:52:53 +02:00
serinko 53db649e84 steps timing explained & italic removed 2023-09-18 11:44:50 +02:00
serinko f76092e8e7 clarified new smooshed gateway 2023-09-18 11:40:58 +02:00
serinko c75fda0eb1 fix SUMMARY.md & links 2023-09-18 10:48:27 +02:00
serinko 9dfbc8c9c8 integrations added to developers/faq section 2023-09-18 10:20:48 +02:00
serinko 0171791188 syntax fix 2023-09-18 07:59:19 +00:00
serinko f6a9d0b843 formatting quotes 2023-09-18 07:45:14 +00:00
serinko 3c750f61e5 syntax fix 2023-09-15 19:14:31 +02:00
serinko a8e7c3ca49 syntax fix 2023-09-15 19:13:11 +02:00
serinko 4f39630861 spell check 2023-09-15 13:39:34 +02:00
serinko 63714092df gathered community questions & nym team answers 2023-09-15 13:33:26 +02:00
serinko cd89f4866a initialize faq/smoosh-faq.md page 2023-09-15 11:58:32 +02:00
serinko a94c4c0895 initialize FAQ section & move faq -> mixnodes-faq 2023-09-15 11:51:24 +02:00
Jędrzej Stuczyński 63b0658c65 [feat] Socks5 and Native client: run with hardcoded topology (#3866)
* allow running clients using hardcoded topology

* fixed sdk/lib/socks5-listener build

* fixed nym-connect build

* allow for both snake_case and camelCase deserialization
2023-09-14 14:26:11 +02:00
Tommy Verrall 6b161700f6 Merge pull request #3882 from nymtech/remove_unecessary_workflows
Github actions: remove nightly builds workflows on latest releases
2023-09-14 13:52:59 +02:00
Tommy Verrall dcfe5f7c5b Merge pull request #3883 from nymtech/ci/fix-clippy-error
fix ci failing builds on clippy errors
2023-09-14 11:31:02 +02:00
Tommy Verrall cd89e26b74 fix ci failing builds on clippy errors 2023-09-14 10:13:32 +02:00
Jon Häggblad 4f9df2a8b1 Update Cargo.lock that was missed during release 2023-09-13 22:28:26 +02:00
Jędrzej Stuczyński f6a4fc3b6f updated mixnet contract schema files 2023-09-13 15:16:08 +01:00
Raphaël Walther 899db660ce Github actions: remove nightly builds workflows on latest releases 2023-09-13 10:22:41 +02:00
Jon Häggblad ec0ac56b8a Improve error handling in wireguard listener (#3881) 2023-09-13 08:19:52 +02:00
Jędrzej Stuczyński 8981ffdcf9 removed queued mixnet migration that was already run (#3872) 2023-09-12 14:10:44 +01:00
Tommy Verrall df25c01771 Merge pull request #3876 from nymtech/mixnet-version
updating mixnet contract version to 1.5.1
2023-09-12 15:08:52 +02:00
Tommy Verrall a11dead84a Merge pull request #3875 from nymtech/release/v1.1.31-kitkat
bump versions and update changelog
2023-09-12 15:08:29 +02:00
benedettadavico 5aa999643f updating mixnet contract version to 1.5.1 2023-09-12 15:06:32 +02:00
Jon Häggblad 96b54db060 Wireguard listener (#3868)
* wip

* wip

* Most channels are in place

* tidy

* Send data to tunnel

* wip: adding in boringtun

* Handle timers

* Add consume_wg

* Split into mod

* Reorder

* Comments

* Refine channel handling

* Sort out dependency conflict

* Move wireguard listener in gateway beind a feature flag
2023-09-12 14:14:46 +02:00
benedettadavico 5bd4295164 bump versions and update changelog 2023-09-12 10:20:34 +02:00
Jędrzej Stuczyński b07627d57e fixed ChangeMixCostParams event deserialization (#3871) 2023-09-11 23:13:18 +01:00
Tommy Verrall c4667a6792 Merge pull request #3846 from nymtech/jon/handle-unable-upgrade-config
clients: handle config upgrade failure
2023-09-08 11:11:43 +02:00
Tommy Verrall 2e2d258e53 Merge pull request #3860 from nymtech/feature/add-nc-wg-android
feat(nc-wireguard): bootstrap android client app
2023-09-08 10:05:00 +02:00
Tommy Verrall 843c74db63 Merge pull request #3865 from nymtech/fix_workflow_on_latest_release
Github actions: fix nightly build workflow
2023-09-08 10:01:41 +02:00
Tommy Verrall 142443b87e Merge pull request #3863 from nymtech/patch/documentation/minor-fix-serinko
DOCS: Fix broken links and syntax flaws
2023-09-08 10:01:20 +02:00
Raphaël Walther ec4765c9c6 Github actions: fix nightly build workflow 2023-09-08 08:47:56 +02:00
pierre 77a56600f0 fix build, add icon launcher 2023-09-07 16:34:37 +02:00
Mark Sinclair 90027dc525 Merge pull request #3864 from nymtech/bugfix/linting
Remove unused import
2023-09-07 15:02:37 +01:00
serinko 6a39e19f2e CLI upgrade link correction 2023-09-07 16:00:34 +02:00
serinko 48140647b7 wallet & port link correction 2023-09-07 15:58:53 +02:00
Mark Sinclair 7d1cb6ca19 Remove unused import 2023-09-07 14:51:55 +01:00
serinko d1f7066eb5 socks-proxy: link correction 2023-09-07 15:01:46 +02:00
serinko 2789951d9a integration-faq: link correction 2023-09-07 14:58:33 +02:00
serinko faab815c79 overview: link correction 2023-09-07 14:57:11 +02:00
serinko a205fecece mixnet-integration: link correction 2023-09-07 14:54:53 +02:00
serinko f3116993d8 note-types: link correction 2023-09-07 14:41:03 +02:00
serinko 4fae075dae wallet-bonding: url correction 2023-09-07 14:39:27 +02:00
serinko da46955817 glossary: links correction 2023-09-07 14:35:43 +02:00
serinko f2fc837811 nym-vs-others: link correction 2023-09-07 14:33:28 +02:00
Jędrzej Stuczyński e7929d6f6b Feature/wasm client nodejs (#3769)
* wip

* post-cherry pick fixes

* wip

* wip

* using sqlite-based indexeddb shim

* running nymClient in worker thread

* improved received handling

* building node mix-fetch

* fixed mix fetch request constructor if args[1] == undefined

* fixed build target

* nodejs origin bypass

* mix fetch in node

but I dont think anyone should use it over normal client...

* target locking

* fixed post-rebasing issues
2023-09-07 13:30:04 +01:00
serinko e8f99cfdbe websocket-client: link correction 2023-09-07 14:26:04 +02:00
serinko b4ccd16d8b ledger-live: link correction 2023-09-07 14:14:32 +02:00
serinko f7974c5db8 rpc-node: link correction 2023-09-07 14:13:46 +02:00
Mark Sinclair 90a925ee62 Add auto-generated contract client (#3861)
* Add auto-generated contract client

* Use HTTPS instead of websockets

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2023-09-07 12:45:48 +01:00
Jędrzej Stuczyński 4890c528bc feat: mixFetch - the final countdown (#3737)
* mixFetch

* clippy

* removed redundant Arc over 'WasmStorage' in the 'ClientStorage'

---------

Co-authored-by: Fouad <fmtabbara@hotmail.co.uk>
2023-09-07 12:45:03 +01:00
serinko d96de448d0 operators/maintenance page: syntax corrections 2023-09-07 13:28:04 +02:00
serinko 74cd6f0198 docs: correcting links to operators pages 2023-09-07 13:27:47 +02:00
Tommy Verrall e71ae7198f Merge pull request #3843 from nymtech/fix/nc-selected-nr
fix(nc): refresh nr list on privacy switch
2023-09-07 12:58:59 +02:00
Tommy Verrall 273f612d46 Merge pull request #3844 from nymtech/feature/named-task-client
feat: add name to `TaskClient`
2023-09-07 12:58:16 +02:00
Mark Sinclair 2e8d318587 Merge pull request #3845 from nymtech/feature/fix_updater_url
Fix updater.json URL
2023-09-07 11:04:41 +01:00
Jędrzej Stuczyński 17bd44f840 added optional name to TaskManager 2023-09-07 10:22:14 +01:00
serinko c1076f81a6 Merge pull request #3857 from nymtech/feature/documentation/dev-portal/faq2
DOCS: dev-portal FAQ section
2023-09-07 09:06:27 +00:00
serinko 26507ee7d3 minor syntax correction 2023-09-07 10:58:38 +02:00
serinko 4677312cad fixed ls command for unknown/allowed.list 2023-09-07 10:42:23 +02:00
serinko 07eddc8187 added link to faq to all intros 2023-09-07 10:34:59 +02:00
pierre b9a9a407e9 init 2023-09-06 20:01:58 +02:00
Mark Sinclair 3c482eff6e Docs: add prod deploy settings 2023-09-06 18:08:56 +01:00
Mark Sinclair 2880049196 Revert "init"
This reverts commit 8d2e8b3d26.
2023-09-06 18:08:17 +01:00
serinko 33bf344d63 initialized faq section, moved existing faqs, started general faq page 2023-09-06 14:09:48 +02:00
Jędrzej Stuczyński 0efa78c4a8 adjusted logging on TaskClient Drop 2023-09-06 12:52:09 +01:00
Jędrzej Stuczyński 32ee16bf0b added task name to 'UnexpectedHalt' error 2023-09-06 12:52:08 +01:00
Jędrzej Stuczyński a8f70fe4a2 few named examples for mixnode 2023-09-06 12:52:08 +01:00
Jędrzej Stuczyński f6fe5d41ea introduced named TaskClient and including the name for logs 2023-09-06 12:52:08 +01:00
Jon Häggblad e336b9948e Collapse conditional 2023-09-06 10:44:04 +02:00
Jon Häggblad 1cf2b10e31 clients: handle config upgrade failure 2023-09-06 10:09:25 +02:00
Bogdan-Ștefan Neacșu b3cd42de58 Fix updater.json URL 2023-09-05 18:46:57 +03:00
pierre f16498915a fix types 2023-09-05 17:02:24 +02:00
pierre d364510400 typo 2023-09-05 16:23:26 +02:00
pierre 96f9e39e1d refresh nr list on privacy mode switch 2023-09-05 16:20:17 +02:00
691 changed files with 48788 additions and 9843 deletions
@@ -83,7 +83,7 @@ jobs:
run: cargo install --version 0.112.0 wasm-opt
- name: Build release contracts
run: make wasm
run: make contracts-wasm
- name: Prepare build output
shell: bash
+6 -1
View File
@@ -87,9 +87,14 @@ jobs:
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
working-directory: dist/docs
- name: Build Project Artifacts
- name: Build Project Artifacts (preview)
if: github.ref != 'refs/heads/master'
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
working-directory: dist/docs
- name: Build Project Artifacts (production)
if: github.ref == 'refs/heads/master'
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
working-directory: dist/docs
- name: Deploy Project Artifacts to Vercel (preview)
if: github.ref != 'refs/heads/master'
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
run: cargo install --version 0.112.0 wasm-opt
- name: Build release contracts
run: make wasm
run: make contracts-wasm
- name: Upload Mixnet Contract Artifact
uses: actions/upload-artifact@v3
-191
View File
@@ -1,191 +0,0 @@
name: Nightly builds on latest release
on:
schedule:
- cron: '14 2 * * *'
jobs:
matrix_prep:
runs-on: ubuntu-20.04
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v3
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
get_release:
runs-on: ubuntu-20.04
needs: matrix_prep
outputs:
output1: ${{ steps.step2.outputs.latest_release }}
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Fetch all branches
run: git fetch --all
- name: Set output variable to latest release branch
id: step2
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
build:
needs: [get_release,matrix_prep]
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
continue-on-error: true
if: matrix.os == 'ubuntu-20.04'
- name: Check out latest release branch
uses: actions/checkout@v3
with:
ref: ${{needs.get_release.outputs.output1}}
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
- name: Build all examples
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --examples
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
- name: Run expensive tests
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace -- --ignored
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
- uses: actions-rs/clippy-check@v1
name: Clippy checks
continue-on-error: true
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets -- -D warnings
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
# nym-wallet (the rust part)
- name: Build nym-wallet rust code
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Run nym-wallet tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Check nym-wallet formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
- name: Run clippy for nym-wallet
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: [build,get_release]
runs-on: custom-runner-linux
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v3
- name: install npm
uses: actions/setup-node@v3
if: env.WORKFLOW_CONCLUSION == 'failure'
with:
node-version: 18
- name: Matrix - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build on latest release"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH_NAME: "${{needs.get_release.outputs.output1}}"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
@@ -1,191 +0,0 @@
name: Nightly builds on second latest release
on:
schedule:
- cron: '24 2 * * *'
jobs:
matrix_prep:
runs-on: ubuntu-20.04
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v3
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
get_release:
runs-on: ubuntu-20.04
needs: matrix_prep
outputs:
output1: ${{ steps.step2.outputs.latest_release }}
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Fetch all branches
run: git fetch --all
- name: Set output variable to latest release branch
id: step2
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 2 | head -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
build:
needs: [get_release,matrix_prep]
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
continue-on-error: true
if: matrix.os == 'ubuntu-20.04'
- name: Check out latest release branch
uses: actions/checkout@v3
with:
ref: ${{needs.get_release.outputs.output1}}
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Build all examples
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --examples
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
- name: Run expensive tests
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace -- --ignored
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- uses: actions-rs/clippy-check@v1
name: Clippy checks
continue-on-error: true
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets -- -D warnings
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
with:
command: clean
# nym-wallet (the rust part)
- name: Build nym-wallet rust code
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Run nym-wallet tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Check nym-wallet formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
- name: Run clippy for nym-wallet
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: [build,get_release]
runs-on: custom-runner-linux
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v3
- name: install npm
uses: actions/setup-node@v3
if: env.WORKFLOW_CONCLUSION == 'failure'
with:
node-version: 18
- name: Matrix - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build on latest release"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH_NAME: "${{needs.get_release.outputs.output1}}"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+27 -3
View File
@@ -4,9 +4,11 @@ on:
push:
paths:
- "sdk/typescript/**"
- "wasm/**"
pull_request:
paths:
- "sdk/typescript/**"
- "wasm/**"
jobs:
build:
@@ -26,17 +28,39 @@ jobs:
toolchain: stable
- name: Setup yarn
run: npm install -g yarn
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
- name: Build branch WASM packages
run: make sdk-wasm-build
- name: Install
run: yarn
- name: Build
run: yarn docs:prod:build
- name: Deploy branch to CI www (docs)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "dist/ts/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/sdk-ts-docs-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Matrix - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
env:
NYM_NOTIFICATION_KIND: ts-packages
NYM_PROJECT_NAME: "ts-packages"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}"
NYM_PROJECT_NAME: "sdk-ts-docs"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}/docs/sdk/typescript"
NYM_CI_WWW_LOCATION: "sdk-ts-docs-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
IS_SUCCESS: "${{ job.status == 'success' }}"
+17 -7
View File
@@ -25,27 +25,37 @@ jobs:
runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
run: sudo apt-get install rsync
continue-on-error: true
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v3
with:
node-version: 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
- name: Setup yarn
run: npm install -g yarn
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
- name: Install
run: yarn
- name: Build packages
run: yarn build
run: yarn build:ci:sdk
- name: Lint
run: yarn lint && yarn tsc
run: yarn lint
- name: Typecheck with tsc
run: yarn tsc
- name: Matrix - Node Install
run: npm install
working-directory: .github/workflows/support-files
-40
View File
@@ -1,40 +0,0 @@
name: Wasm Client
on:
pull_request:
paths:
- 'clients/webassembly/**'
- 'clients/client-core/**'
- 'common/**'
- 'contracts/**'
- 'gateway/gateway-requests/**'
- 'nym-api/nym-api-requests/**'
jobs:
wasm:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path clients/webassembly/Cargo.toml -- --check
- uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
+46
View File
@@ -0,0 +1,46 @@
name: Wasm Client
on:
pull_request:
paths:
- 'wasm/**'
- 'clients/client-core/**'
- 'common/**'
jobs:
wasm:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install wasm-opt
run: cargo install wasm-opt
- name: Install wasm-bindgen-cli
run: cargo install wasm-bindgen-cli
- name: "Build"
run: make sdk-wasm-build
- name: "Test"
run: make sdk-wasm-test
- name: "Lint"
run: make sdk-wasm-lint
+3 -1
View File
@@ -43,4 +43,6 @@ envs/qwerty.env
.parcel-cache
**/.DS_Store
cpu-cycles/libcpucycles/build
foxyfox.env
foxyfox.env
.next
+16
View File
@@ -4,6 +4,22 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [v1.1.31-kitkat] (2023-09-12)
- feat: add name to `TaskClient` ([#3844])
- added 'open_proxy', 'enabled_statistics' and 'statistics_recipient' to NR config ([#3839])
- MixFetch: initial prototype for insecure HTTP ([#3645])
- MixFetch: prototype implementing TLS in WASM for HTTPS ([#3644])
- SDK: build package for NodeJS ([#3558])
- [Issue] There is already an open connection to this client ([#2845])
[#3844]: https://github.com/nymtech/nym/pull/3844
[#3839]: https://github.com/nymtech/nym/pull/3839
[#3645]: https://github.com/nymtech/nym/issues/3645
[#3644]: https://github.com/nymtech/nym/issues/3644
[#3558]: https://github.com/nymtech/nym/issues/3558
[#2845]: https://github.com/nymtech/nym/issues/2845
## [v1.1.30-twix] (2023-09-05)
- geo_aware_provider: fix too much filtering of gateways ([#3826])
Generated
+832 -86
View File
File diff suppressed because it is too large Load Diff
+22 -4
View File
@@ -46,6 +46,7 @@ members = [
"common/crypto",
"common/dkg",
"common/execute",
"common/http-requests",
"common/inclusion-probability",
"common/ledger",
"common/mixnode-common",
@@ -73,7 +74,10 @@ members = [
"common/task",
"common/topology",
"common/types",
"common/wasm-utils",
"common/wasm/client-core",
"common/wasm/storage",
"common/wasm/utils",
"common/wireguard",
"explorer-api",
"explorer-api/explorer-api-requests",
"explorer-api/explorer-client",
@@ -87,11 +91,18 @@ members = [
"service-providers/network-requester",
"service-providers/network-statistics",
"nym-api",
"nym-browser-extension/storage",
"nym-api/nym-api-requests",
"nym-outfox",
"tools/internal/ssl-inject",
"tools/internal/sdk-version-bump",
"tools/nym-cli",
"tools/nym-nr-query",
"tools/ts-rs-cli"
"tools/ts-rs-cli",
"wasm/client",
"wasm/full-nym-wasm",
"wasm/mix-fetch",
"wasm/node-tester",
]
default-members = [
@@ -105,7 +116,7 @@ default-members = [
"explorer-api",
]
exclude = ["explorer", "contracts", "clients/webassembly", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-browser-extension/storage", "cpu-cycles"]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "cpu-cycles"]
[workspace.package]
authors = ["Nym Technologies SA"]
@@ -117,7 +128,7 @@ license = "Apache-2.0"
[workspace.dependencies]
anyhow = "1.0.71"
async-trait = "0.1.64"
async-trait = "0.1.68"
bip39 = { version = "2.0.0", features = ["zeroize"] }
cfg-if = "1.0.0"
cosmwasm-derive = "=1.3.0"
@@ -156,4 +167,11 @@ url = "2.4"
zeroize = "1.6.0"
# wasm-related dependencies
gloo-utils = "0.1.7"
js-sys = "0.3.63"
serde-wasm-bindgen = "0.5.0"
tsify = "0.4.5"
wasm-bindgen = "0.2.86"
wasm-bindgen-futures = "0.4.37"
wasmtimer = "0.2.0"
web-sys = "0.3.63"
+76 -8
View File
@@ -1,17 +1,21 @@
# Default target
all: test
test: clippy-all cargo-test wasm fmt
test: clippy-all cargo-test contracts-wasm sdk-wasm-test fmt
test-all: test cargo-test-expensive
no-clippy: build cargo-test wasm fmt
no-clippy: build cargo-test contracts-wasm fmt fmt-browser-extension-storage
happy: fmt clippy-happy test
build: sdk-wasm-build build-browser-extension-storage
# Building release binaries is a little manual as we can't just build --release
# on all workspaces.
build-release: build-release-main wasm
build-release: build-release-main contracts-wasm
clippy: sdk-wasm-lint clippy-browser-extension-storage
# Deprecated
# For backwards compatibility
@@ -43,6 +47,9 @@ test-$(1):
test-expensive-$(1):
cargo test --manifest-path $(2)/Cargo.toml --workspace -- --ignored
build-standalone-$(1):
cargo build --manifest-path $(2)/Cargo.toml $(3)
build-$(1):
cargo build --manifest-path $(2)/Cargo.toml --workspace $(3)
@@ -74,7 +81,7 @@ endef
$(eval $(call add_cargo_workspace,main,.))
$(eval $(call add_cargo_workspace,contracts,contracts,--lib --target wasm32-unknown-unknown))
$(eval $(call add_cargo_workspace,wasm-client,clients/webassembly,--target wasm32-unknown-unknown))
#$(eval $(call add_cargo_workspace,wasm-client,clients/webassembly,--target wasm32-unknown-unknown))
$(eval $(call add_cargo_workspace,wallet,nym-wallet,))
$(eval $(call add_cargo_workspace,connect,nym-connect/desktop))
@@ -88,6 +95,67 @@ build-explorer-api:
build-nym-cli:
cargo build -p nym-cli --release
build-browser-extension-storage:
cargo build -p extension-storage --target wasm32-unknown-unknown
fmt-browser-extension-storage:
cargo fmt -p extension-storage -- --check
clippy-browser-extension-storage:
cargo clippy -p extension-storage --target wasm32-unknown-unknown -- -Dwarnings
sdk-wasm: sdk-wasm-build sdk-wasm-test sdk-wasm-lint
sdk-wasm-build:
# browser storage
$(MAKE) -C nym-browser-extension/storage wasm-pack
# client
$(MAKE) -C wasm/client build
# node-tester
$(MAKE) -C wasm/node-tester build
# mix-fetch
$(MAKE) -C wasm/mix-fetch build
# full
$(MAKE) -C wasm/full-nym-wasm build-full
# run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo
sdk-typescript-build:
lerna run --scope @nymproject/sdk build --stream
lerna run --scope @nymproject/mix-fetch build --stream
lerna run --scope @nymproject/node-tester build --stream
sdk-wasm-test:
# # client
# cargo test -p nym-client-wasm --target wasm32-unknown-unknown
#
# # node-tester
# cargo test -p nym-node-tester-wasm --target wasm32-unknown-unknown
#
# # mix-fetch
# #cargo test -p nym-wasm-sdk --target wasm32-unknown-unknown
#
# # full
# cargo test -p nym-wasm-sdk --target wasm32-unknown-unknown
sdk-wasm-lint:
# client
cargo clippy -p nym-client-wasm --target wasm32-unknown-unknown -- -Dwarnings
# node-tester
cargo clippy -p nym-node-tester-wasm --target wasm32-unknown-unknown -- -Dwarnings
# mix-fetch
$(MAKE) -C wasm/mix-fetch check-fmt
# full
cargo clippy -p nym-wasm-sdk --target wasm32-unknown-unknown -- -Dwarnings
# -----------------------------------------------------------------------------
# Build contracts ready for deploy
# -----------------------------------------------------------------------------
@@ -98,12 +166,12 @@ MIXNET_CONTRACT=$(CONTRACTS_OUT_DIR)/mixnet_contract.wasm
SERVICE_PROVIDER_DIRECTORY_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_service_provider_directory.wasm
NAME_SERVICE_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_name_service.wasm
wasm: wasm-build wasm-opt
contracts-wasm: contracts-wasm-build contracts-wasm-opt
wasm-build:
contracts-wasm-build:
RUSTFLAGS='-C link-arg=-s' cargo build --lib --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
wasm-opt:
contracts-wasm-opt:
wasm-opt --disable-sign-ext -Os $(VESTING_CONTRACT) -o $(VESTING_CONTRACT)
wasm-opt --disable-sign-ext -Os $(MIXNET_CONTRACT) -o $(MIXNET_CONTRACT)
wasm-opt --disable-sign-ext -Os $(SERVICE_PROVIDER_DIRECTORY_CONTRACT) -o $(SERVICE_PROVIDER_DIRECTORY_CONTRACT)
@@ -117,7 +185,7 @@ contract-schema:
# -----------------------------------------------------------------------------
# NOTE: this seems deprecated an not needed anymore?
mixnet-opt: wasm
mixnet-opt: contracts-wasm
cd contracts/mixnet && make opt
generate-typescript:
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.28"
version = "1.1.29"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -1,8 +1,13 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::persistence::ClientPaths;
use crate::client::config::{default_config_filepath, Config, Socket, SocketType};
use crate::{
client::config::{
default_config_filepath, persistence::ClientPaths, Config, Socket, SocketType,
},
error::ClientError,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseConfigV1_1_20_2;
@@ -43,18 +48,18 @@ impl ConfigV1_1_20_2 {
// in this upgrade, gateway endpoint configuration was moved out of the config file,
// so its returned to be stored elsewhere.
pub fn upgrade(self) -> (Config, GatewayEndpointConfig) {
pub fn upgrade(self) -> Result<(Config, GatewayEndpointConfig), ClientError> {
let gateway_details = self.base.client.gateway_endpoint.clone().into();
let config = Config {
base: self.base.into(),
socket: self.socket.into(),
storage_paths: ClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default(),
common_paths: self.storage_paths.common_paths.upgrade_default()?,
},
logging: self.logging,
};
(config, gateway_details)
Ok((config, gateway_details))
}
}
+14 -3
View File
@@ -21,6 +21,7 @@ use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
use nym_validator_client::QueryHttpRpcNyxdClient;
use std::error::Error;
use std::path::PathBuf;
use tokio::sync::watch::error::SendError;
pub use nym_sphinx::addressing::clients::Recipient;
@@ -34,11 +35,17 @@ pub struct SocketClient {
/// Client configuration options, including, among other things, packet sending rates,
/// key filepaths, etc.
config: Config,
/// Optional path to a .json file containing standalone network details.
custom_mixnet: Option<PathBuf>,
}
impl SocketClient {
pub fn new(config: Config) -> Self {
SocketClient { config }
pub fn new(config: Config, custom_mixnet: Option<PathBuf>) -> Self {
SocketClient {
config,
custom_mixnet,
}
}
fn start_websocket_listener(
@@ -109,7 +116,11 @@ impl SocketClient {
let storage = self.initialise_storage().await?;
let base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client);
let mut base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client);
if let Some(custom_mixnet) = &self.custom_mixnet {
base_client = base_client.with_stored_topology(custom_mixnet)?;
}
Ok(base_client)
}
+29 -4
View File
@@ -15,12 +15,15 @@ use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::helpers::current_gateways;
use nym_client_core::init::GatewaySetup;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use nym_topology::NymTopology;
use serde::Serialize;
use std::fmt::Display;
use std::net::IpAddr;
use std::path::PathBuf;
use std::{fs, io};
use tap::TapFallible;
@@ -49,7 +52,12 @@ pub(crate) struct Init {
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[clap(long, alias = "api_validators", value_delimiter = ',')]
#[clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)]
// the alias here is included for backwards compatibility (1.1.4 and before)
nym_apis: Option<Vec<url::Url>>,
@@ -65,6 +73,10 @@ pub(crate) struct Init {
#[clap(long)]
host: Option<IpAddr>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
@@ -130,7 +142,7 @@ fn init_paths(id: &str) -> io::Result<()> {
fs::create_dir_all(default_config_directory(id))
}
pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
pub(crate) async fn execute(args: Init) -> Result<(), ClientError> {
eprintln!("Initialising client...");
let id = &args.id;
@@ -173,12 +185,25 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let init_details = nym_client_core::init::setup_gateway(
let network_gateways = if let Some(hardcoded_topology) = args
.custom_mixnet
.map(NymTopology::new_from_file)
.transpose()?
{
// hardcoded_topology
hardcoded_topology.get_gateways()
} else {
let mut rng = rand::thread_rng();
current_gateways(&mut rng, &config.base.client.nym_api_urls).await?
};
let init_details = nym_client_core::init::setup_gateway_from(
gateway_setup,
&key_store,
&details_store,
register_gateway,
Some(&config.base.client.nym_api_urls),
Some(&network_gateways),
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?
+5 -5
View File
@@ -84,8 +84,8 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
let bin_name = "nym-native-client";
match args.command {
Commands::Init(m) => init::execute(&m).await?,
Commands::Run(m) => run::execute(&m).await?,
Commands::Init(m) => init::execute(m).await?,
Commands::Run(m) => run::execute(m).await?,
Commands::BuildInfo(m) => build_info::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
@@ -157,7 +157,7 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
let updated_step1: ConfigV1_1_20 = old_config.into();
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let (updated, gateway_config) = updated_step2.upgrade();
let (updated, gateway_config) = updated_step2.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
@@ -177,7 +177,7 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated, gateway_config) = updated_step1.upgrade();
let (updated, gateway_config) = updated_step1.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
@@ -194,7 +194,7 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
info!("It seems the client is using <= v1.1.20_2 config template.");
info!("It is going to get updated to the current specification.");
let (updated, gateway_config) = old_config.upgrade();
let (updated, gateway_config) = old_config.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
+15 -3
View File
@@ -13,6 +13,7 @@ use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_crypto::asymmetric::identity;
use std::error::Error;
use std::net::IpAddr;
use std::path::PathBuf;
#[derive(Args, Clone)]
pub(crate) struct Run {
@@ -25,7 +26,12 @@ pub(crate) struct Run {
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[clap(long, alias = "api_validators", value_delimiter = ',')]
#[clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)]
// the alias here is included for backwards compatibility (1.1.4 and before)
nym_apis: Option<Vec<url::Url>>,
@@ -46,6 +52,10 @@ pub(crate) struct Run {
#[clap(long)]
host: Option<IpAddr>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
@@ -95,7 +105,7 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn Error + Send + Sync>> {
eprintln!("Starting client {}...", args.id);
let mut config = try_load_current_config(&args.id)?;
@@ -106,5 +116,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Syn
return Err(Box::new(ClientError::FailedLocalVersionCheck));
}
SocketClient::new(config).run_socket_forever().await
SocketClient::new(config, args.custom_mixnet)
.run_socket_forever()
.await
}
@@ -230,8 +230,7 @@ impl ServerResponse {
let error_kind = ErrorKind::try_from(b[1])?;
let message_len =
u64::from_be_bytes(b[2..2 + size_of::<u64>()].as_ref().try_into().unwrap());
let message_len = u64::from_be_bytes(b[2..2 + size_of::<u64>()].try_into().unwrap());
let message = &b[2 + size_of::<u64>()..];
if message.len() as u64 != message_len {
return Err(error::Error::new(
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.28"
version = "1.1.29"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -16,6 +16,7 @@ serde_json = { workspace = true }
tap = "1.0.1"
thiserror = { workspace = true }
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
rand = "0.7.3"
url = { workspace = true }
# internal
+23 -3
View File
@@ -14,11 +14,14 @@ use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::helpers::current_gateways;
use nym_client_core::init::GatewaySetup;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use nym_topology::NymTopology;
use serde::Serialize;
use std::fmt::Display;
use std::path::PathBuf;
use std::{fs, io};
use tap::TapFallible;
@@ -68,6 +71,10 @@ pub(crate) struct Init {
#[clap(short, long)]
port: Option<u16>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
@@ -138,7 +145,7 @@ fn init_paths(id: &str) -> io::Result<()> {
fs::create_dir_all(default_config_directory(id))
}
pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
pub(crate) async fn execute(args: Init) -> Result<(), Socks5ClientError> {
eprintln!("Initialising client...");
let id = &args.id;
@@ -185,12 +192,25 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let init_details = nym_client_core::init::setup_gateway(
let network_gateways = if let Some(hardcoded_topology) = args
.custom_mixnet
.map(NymTopology::new_from_file)
.transpose()?
{
// hardcoded_topology
hardcoded_topology.get_gateways()
} else {
let mut rng = rand::thread_rng();
current_gateways(&mut rng, &config.core.base.client.nym_api_urls).await?
};
let init_details = nym_client_core::init::setup_gateway_from(
gateway_setup,
&key_store,
&details_store,
register_gateway,
Some(&config.core.base.client.nym_api_urls),
Some(&network_gateways),
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?
+5 -5
View File
@@ -87,8 +87,8 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
let bin_name = "nym-socks5-client";
match args.command {
Commands::Init(m) => init::execute(&m).await?,
Commands::Run(m) => run::execute(&m).await?,
Commands::Init(m) => init::execute(m).await?,
Commands::Run(m) => run::execute(m).await?,
Commands::BuildInfo(m) => build_info::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
@@ -199,7 +199,7 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError> {
let updated_step1: ConfigV1_1_20 = old_config.into();
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let (updated, gateway_config) = updated_step2.upgrade();
let (updated, gateway_config) = updated_step2.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
@@ -219,7 +219,7 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, Socks5ClientError> {
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated, gateway_config) = updated_step1.upgrade();
let (updated, gateway_config) = updated_step1.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
@@ -236,7 +236,7 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, Socks5ClientError> {
info!("It seems the client is using <= v1.1.20_2 config template.");
info!("It is going to get updated to the current specification.");
let (updated, gateway_config) = old_config.upgrade();
let (updated, gateway_config) = old_config.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
+11 -4
View File
@@ -15,6 +15,7 @@ use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
use nym_crypto::asymmetric::identity;
use nym_socks5_client_core::NymClient;
use nym_sphinx::addressing::clients::Recipient;
use std::path::PathBuf;
#[derive(Args, Clone)]
pub(crate) struct Run {
@@ -45,13 +46,17 @@ pub(crate) struct Run {
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the Nym APIs
#[clap(long, value_delimiter = ',')]
#[clap(long, value_delimiter = ',', group = "network")]
nym_apis: Option<Vec<url::Url>>,
/// Port for the socket to listen on
#[clap(short, long)]
port: Option<u16>,
/// Path to .json file containing custom network specification.
#[clap(long, group = "network", group = "routing", hide = true)]
custom_mixnet: Option<PathBuf>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
@@ -62,7 +67,7 @@ pub(crate) struct Run {
no_cover: bool,
/// Set geo-aware mixnode selection when sending mixnet traffic, for experiments only.
#[clap(long, hide = true, value_parser = validate_country_group)]
#[clap(long, hide = true, value_parser = validate_country_group, group="routing")]
geo_routing: Option<CountryGroup>,
/// Enable medium mixnet traffic, for experiments only.
@@ -124,7 +129,7 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
eprintln!("Starting client {}...", args.id);
let mut config = try_load_current_config(&args.id)?;
@@ -138,5 +143,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error
let storage =
OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug)
.await?;
NymClient::new(config.core, storage).run_forever().await
NymClient::new(config.core, storage, args.custom_mixnet)
.run_forever()
.await
}
@@ -1,17 +1,21 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::SocksClientPaths;
use crate::config::{default_config_filepath, Config};
use crate::{
config::{default_config_filepath, persistence::SocksClientPaths, Config},
error::Socks5ClientError,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::GatewayEndpointConfig;
use nym_config::read_config_from_toml_file;
pub use nym_socks5_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as CoreConfigV1_1_20_2;
use serde::{Deserialize, Serialize};
use std::io;
use std::path::Path;
pub use nym_socks5_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as CoreConfigV1_1_20_2;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct SocksClientPathsV1_1_20_2 {
#[serde(flatten)]
@@ -39,16 +43,16 @@ impl ConfigV1_1_20_2 {
// in this upgrade, gateway endpoint configuration was moved out of the config file,
// so its returned to be stored elsewhere.
pub fn upgrade(self) -> (Config, GatewayEndpointConfig) {
pub fn upgrade(self) -> Result<(Config, GatewayEndpointConfig), Socks5ClientError> {
let gateway_details = self.core.base.client.gateway_endpoint.clone().into();
let config = Config {
core: self.core.into(),
storage_paths: SocksClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default(),
common_paths: self.storage_paths.common_paths.upgrade_default()?,
},
logging: self.logging,
};
(config, gateway_details)
Ok((config, gateway_details))
}
}
-11
View File
@@ -1,11 +0,0 @@
install:
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -V
- cargo -V
build: false
test_script:
- cargo test --locked
View File
-2
View File
@@ -1,2 +0,0 @@
[build]
target = "wasm32-unknown-unknown"
-6
View File
@@ -1,6 +0,0 @@
/target
**/*.rs.bk
bin/
pkg/
wasm-pack.log
node_modules
-69
View File
@@ -1,69 +0,0 @@
language: rust
sudo: false
cache: cargo
matrix:
include:
# Builds with wasm-pack.
- rust: beta
env: RUST_BACKTRACE=1
addons:
firefox: latest
chrome: stable
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
script:
- cargo generate --git . --name testing
# Having a broken Cargo.toml (in that it has curlies in fields) anywhere
# in any of our parent dirs is problematic.
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- wasm-pack build
- wasm-pack test --chrome --firefox --headless
# Builds on nightly.
- rust: nightly
env: RUST_BACKTRACE=1
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- rustup target add wasm32-unknown-unknown
script:
- cargo generate --git . --name testing
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- cargo check
- cargo check --target wasm32-unknown-unknown
- cargo check --no-default-features
- cargo check --target wasm32-unknown-unknown --no-default-features
- cargo check --no-default-features --features console_error_panic_hook
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
- cargo check --no-default-features --features "console_error_panic_hook wee_alloc"
- cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc"
# Builds on beta.
- rust: beta
env: RUST_BACKTRACE=1
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- rustup target add wasm32-unknown-unknown
script:
- cargo generate --git . --name testing
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- cargo check
- cargo check --target wasm32-unknown-unknown
- cargo check --no-default-features
- cargo check --target wasm32-unknown-unknown --no-default-features
- cargo check --no-default-features --features console_error_panic_hook
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
# Note: no enabling the `wee_alloc` feature here because it requires
# nightly for now.
-5559
View File
File diff suppressed because it is too large Load Diff
-74
View File
@@ -1,74 +0,0 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.1.1"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy"]
license = "Apache-2.0"
repository = "https://github.com/nymtech/nym"
description = "A webassembly client which can be used to interact with the the Nym privacy platform. Wasm is used for Sphinx packet generation."
rust-version = "1.56"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
offline-test = []
[dependencies]
async-trait = "0.1.68"
bs58 = "0.4.0"
futures = "0.3"
js-sys = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"
serde-wasm-bindgen = "0.5"
tokio = { version = "1.24.1", features = ["sync"] }
url = "2.2"
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
thiserror = "1.0.40"
zeroize = "1.6.0"
wasmtimer = { version = "0.2.0", features = ["tokio"] }
# internal
nym-node-tester-utils = { path = "../../common/node-tester-utils" }
nym-client-core = { path = "../../common/client-core", default-features = false, features = ["wasm"] }
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-sphinx-acknowledgements = { path = "../../common/nymsphinx/acknowledgements", features = ["serde"]}
nym-topology = { path = "../../common/topology" }
nym-gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
nym-task = { path = "../../common/task" }
wasm-utils = { path = "../../common/wasm-utils", features = ["storage"], default-features = false }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1", optional = true }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
#
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3"
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
[profile.release]
lto = true
opt-level = 'z'
-176
View File
@@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
-8
View File
@@ -1,8 +0,0 @@
clippy:
cargo clippy --all-features --target wasm32-unknown-unknown -- -D warnings
test:
wasm-pack test --node
wasm-build:
wasm-pack build
-382
View File
@@ -1,382 +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.
importScripts("nym_client_wasm.js");
console.log("Initializing worker");
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
const {
NymNodeTester,
WasmGateway,
WasmMixNode,
WasmNymTopology,
default_debug,
NymClientBuilder,
NymClient,
set_panic_hook,
Config,
GatewayEndpointConfig,
ClientStorage,
current_network_topology,
make_key,
make_key2,
} = wasm_bindgen;
let client = null;
let tester = null;
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
function dummyTopology() {
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);
}
function printAndDisplayTestResult(result) {
result.log_details();
self.postMessage({
kind: "DisplayTesterResults",
args: {
score: result.score(),
sentPackets: result.sent_packets,
receivedPackets: result.received_packets,
receivedAcks: result.received_acks,
duplicatePackets: result.duplicate_packets,
duplicateAcks: result.duplicate_acks,
},
});
}
async function testWithTester() {
// A) construct with hardcoded topology
const topology = dummyTopology();
const nodeTester = await new NymNodeTester(topology, preferredGateway);
// B) first get topology directly from nym-api
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const topology = await current_network_topology(validator)
// const nodeTester = await new NymNodeTester(topology, preferredGateway);
//
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const nodeTester = await NymNodeTester.new_with_api(validator, preferredGateway)
// B) first get topology directly from nym-api
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const topology = await current_network_topology(validator)
// const nodeTester = await new NymNodeTester(topology, undefined, preferredGateway);
//
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const nodeTester = await NymNodeTester.new_with_api(validator, undefined, preferredGateway)
// D, E, F) you also don't have to specify the gateway. if you don't, a random one (from your topology) will be used
// const topology = dummyTopology()
// const nodeTester = await new NymNodeTester(topology);
self.onmessage = async (event) => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case "TestPacket": {
const { mixnodeIdentity } = event.data.args;
console.log("starting node test...");
let result = await nodeTester.test_node(mixnodeIdentity);
printAndDisplayTestResult(result);
}
}
}
};
}
async function testerReconnection() {
const validator = "https://qwerty-validator-api.qa.nymte.ch/api";
const nodeTester = await NymNodeTester.new_with_api(validator);
self.onmessage = async (event) => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case "TestPacket": {
const { mixnodeIdentity } = event.data.args;
console.log("starting node test...");
let result1 = await nodeTester.test_node(mixnodeIdentity);
console.log("sleeping for 5s");
await new Promise((r) => setTimeout(r, 5000));
await nodeTester.disconnect_from_gateway();
console.log("sleeping for 5s");
await new Promise((r) => setTimeout(r, 5000));
await nodeTester.reconnect_to_gateway();
let result2 = await nodeTester.test_node(mixnodeIdentity);
printAndDisplayTestResult(result1);
printAndDisplayTestResult(result2);
}
}
}
};
}
async function testWithNymClient() {
const topology = dummyTopology();
let received = 0;
const onMessageHandler = (message) => {
received += 1;
self.postMessage({
kind: "ReceiveMessage",
args: {
message,
senderTag: undefined,
isTestPacket: true,
},
});
// it's really up to the user to create proper callback here...
console.log(`received ${received} packets so far`);
};
console.log("Instantiating WASM client...");
let clientBuilder = NymClientBuilder.new_tester(
topology,
onMessageHandler,
preferredGateway
);
console.log("Web worker creating WASM client...");
let local_client = await clientBuilder.start_client();
console.log("WASM client running!");
const selfAddress = local_client.self_address();
// set the global (I guess we don't have to anymore?)
client = local_client;
console.log(`Client address is ${selfAddress}`);
self.postMessage({
kind: "Ready",
args: {
selfAddress,
},
});
// Set callback to handle messages passed to the worker.
self.onmessage = async (event) => {
console.log(event);
if (event.data && event.data.kind) {
switch (event.data.kind) {
case "SendMessage": {
const { message, recipient } = event.data.args;
let uint8Array = new TextEncoder().encode(message);
await client.send_regular_message(uint8Array, recipient);
break;
}
case "TestPacket": {
const { mixnodeIdentity } = event.data.args;
const req = await client.try_construct_test_packet_request(
mixnodeIdentity
);
await client.change_hardcoded_topology(req.injectable_topology());
await client.try_send_test_packets(req);
break;
}
}
}
};
}
async function normalNymClientUsage() {
self.postMessage({ kind: "DisableMagicTestButton" });
// only really useful if you want to adjust some settings like traffic rate
// (if not needed you can just pass a null)
const debug = default_debug();
debug.disable_main_poisson_packet_distribution = true;
debug.disable_loop_cover_traffic_stream = true;
debug.use_extended_packet_size = false;
// debug.average_packet_delay_ms = BigInt(10);
// debug.average_ack_delay_ms = BigInt(10);
// debug.ack_wait_addition_ms = BigInt(3000);
// debug.ack_wait_multiplier = 10;
debug.topology_refresh_rate_ms = BigInt(60000);
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
const validator = "https://qwerty-validator-api.qa.nymte.ch/api";
const config = new Config("my-awesome-wasm-client", validator, debug);
const onMessageHandler = (message) => {
console.log(message);
self.postMessage({
kind: "ReceiveMessage",
args: {
message,
},
});
};
console.log("Instantiating WASM client...");
let localClient = await new NymClient(config, onMessageHandler);
console.log("WASM client running!");
const selfAddress = localClient.self_address();
// set the global (I guess we don't have to anymore?)
client = localClient;
console.log(`Client address is ${selfAddress}`);
self.postMessage({
kind: "Ready",
args: {
selfAddress,
},
});
// Set callback to handle messages passed to the worker.
self.onmessage = async (event) => {
console.log(event);
if (event.data && event.data.kind) {
switch (event.data.kind) {
case "SendMessage": {
const { message, recipient } = event.data.args;
let uint8Array = new TextEncoder().encode(message);
await client.send_regular_message(uint8Array, recipient);
break;
}
}
}
};
}
async function messWithStorage() {
self.onmessage = async (event) => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case "TestPacket": {
const { mixnodeIdentity } = event.data.args;
console.log("button clicked...", mixnodeIdentity);
let id1 = "one";
let id2 = "two";
console.log("making store1 NO-ENC");
let _storage1 = await ClientStorage.new_unencrypted(id1);
console.log("making store2 ENC");
let _storage2 = await new ClientStorage(id2, "my-secret-password");
//
//
//
// console.log("attempting to use store1 WITH PASSWORD")
// let _storage1_alt = await new ClientStorage(id1, "password");
//
//
//
// console.log("attempting to use store2 WITHOUT PASSWORD")
// let _storage2_alt = await ClientStorage.new_unencrypted(id2);
//
//
//
// console.log("attempting to use store2 with WRONG PASSWORD")
// let _storage2_bad = await new ClientStorage(id2, "bad-password")
//
// console.log("read1: ", await storage1.read());
// console.log("read2: ", await storage2.read());
//
// console.log("store1: ", await storage1.store("FOOMP"));
//
// console.log("read1: ", await storage1.read());
// console.log("read2: ", await storage2.read());
}
}
}
};
}
async function main() {
// load WASM package
await wasm_bindgen("nym_client_wasm_bg.wasm");
console.log("Loaded WASM");
// sets up better stack traces in case of in-rust panics
set_panic_hook();
// show reconnection capabilities
// await testerReconnection()
// run test on simplified and dedicated tester:
await testWithTester();
// 'Normal' client setup (to send 'normal' messages)
// await normalNymClientUsage()
}
// Let's get started!
main();
-5
View File
@@ -1,5 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) const NODE_TESTER_ID: &str = "_nym-node-tester";
pub(crate) const NODE_TESTER_CLIENT_ID: &str = "_nym-node-tester-client";
@@ -1,45 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::GatewayEndpointConfig;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpointConfig {
let validator_client =
nym_validator_client::client::NymApiClient::new(api_server.parse().unwrap());
let gateways = match validator_client.get_cached_gateways().await {
Err(err) => panic!("failed to obtain list of all gateways - {err}"),
Ok(gateways) => gateways,
};
if let Some(preferred) = preferred {
if let Some(details) = gateways
.iter()
.find(|g| g.gateway.identity_key == preferred)
{
return GatewayEndpointConfig {
gateway_id: details.gateway.identity_key.clone(),
gateway_owner: details.owner.to_string(),
gateway_listener: format!(
"ws://{}:{}",
details.gateway.host, details.gateway.clients_port
),
};
}
}
let details = gateways
.first()
.expect("current topology holds no gateways");
GatewayEndpointConfig {
gateway_id: details.gateway.identity_key.clone(),
gateway_owner: details.owner.to_string(),
gateway_listener: format!(
"ws://{}:{}",
details.gateway.host, details.gateway.clients_port
),
}
}
-42
View File
@@ -1,42 +0,0 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// After reading https://github.com/rust-lang/rust-clippy/issues/11382
// I suspect we *maybe* have hit a false positive, but I'm not sure.
#![allow(clippy::arc_with_non_send_sync)]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
mod client;
#[cfg(target_arch = "wasm32")]
pub mod encoded_payload_helper;
#[cfg(target_arch = "wasm32")]
pub mod error;
#[cfg(target_arch = "wasm32")]
pub mod gateway_selector;
#[cfg(target_arch = "wasm32")]
pub mod storage;
#[cfg(target_arch = "wasm32")]
pub mod tester;
#[cfg(target_arch = "wasm32")]
pub mod topology;
#[cfg(target_arch = "wasm32")]
pub mod validation;
#[cfg(target_arch = "wasm32")]
mod helpers;
mod constants;
#[wasm_bindgen]
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
-28
View File
@@ -1,28 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
use wasm_bindgen::JsValue;
use wasm_utils::simple_js_error;
use wasm_utils::storage::error::StorageError;
#[derive(Debug, Error)]
pub enum ClientStorageError {
#[error("failed to use the storage: {source}")]
StorageError {
#[from]
source: StorageError,
},
#[error("{typ} cryptographic key is not available in storage")]
CryptoKeyNotInStorage { typ: String },
#[error("the prior gateway details are not available in the storage")]
GatewayDetailsNotInStorage,
}
impl From<ClientStorageError> for JsValue {
fn from(value: ClientStorageError) -> Self {
simple_js_error(value.to_string())
}
}
-294
View File
@@ -1,294 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::storage::error::ClientStorageError;
use js_sys::Promise;
use nym_client_core::client::base_client::storage::gateway_details::PersistedGatewayDetails;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_client::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage};
use wasm_utils::PromisableResult;
use zeroize::Zeroizing;
pub(crate) mod error;
pub(crate) mod traits;
const STORAGE_NAME_PREFIX: &str = "wasm-client-storage";
const STORAGE_VERSION: u32 = 1;
// v1 tables
mod v1 {
// stores
pub const KEYS_STORE: &str = "keys";
pub const CORE_STORE: &str = "core";
// keys
pub const CONFIG: &str = "config";
pub const GATEWAY_DETAILS: &str = "gateway_details";
pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair";
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair";
// TODO: for those we could actually use the subtle crypto storage
pub const AES128CTR_ACK_KEY: &str = "aes128ctr_ack_key";
pub const AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS: &str = "aes128ctr_blake3_hmac_gateway_keys";
}
#[wasm_bindgen]
pub struct ClientStorage {
#[allow(dead_code)]
pub(crate) name: String,
pub(crate) inner: Arc<WasmStorage>,
}
#[wasm_bindgen]
impl ClientStorage {
fn db_name(client_id: &str) -> String {
format!("{STORAGE_NAME_PREFIX}-{client_id}")
}
pub(crate) async fn new_async(
client_id: &str,
passphrase: Option<String>,
) -> Result<Self, ClientStorageError> {
let name = Self::db_name(client_id);
// make sure the password is zeroized when no longer used, especially if we error out.
// special care must be taken on JS side to ensure it's correctly used there.
let passphrase = Zeroizing::new(passphrase);
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
// works with an unsigned integer.
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
let old_version = evt.old_version() as u32;
if old_version < 1 {
// migrating to version 1
let db = evt.db();
db.create_object_store(v1::KEYS_STORE)?;
db.create_object_store(v1::CORE_STORE)?;
}
Ok(())
});
let inner = WasmStorage::new(
&name,
STORAGE_VERSION,
migrate_fn,
passphrase.as_ref().map(|p| p.as_bytes()),
)
.await?;
Ok(ClientStorage {
inner: Arc::new(inner),
name,
})
}
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(client_id: String, passphrase: String) -> Promise {
future_to_promise(async move {
Self::new_async(&client_id, Some(passphrase))
.await
.into_promise_result()
})
}
pub fn new_unencrypted(client_id: String) -> Promise {
future_to_promise(async move {
Self::new_async(&client_id, None)
.await
.into_promise_result()
})
}
// TODO: persist client's config
#[allow(dead_code)]
pub(crate) async fn read_config(&self) -> Result<Option<Config>, ClientStorageError> {
self.inner
.read_value(v1::CORE_STORE, JsValue::from_str(v1::CONFIG))
.await
.map_err(Into::into)
}
pub(crate) async fn may_read_gateway_details(
&self,
) -> Result<Option<PersistedGatewayDetails>, ClientStorageError> {
self.inner
.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_DETAILS))
.await
.map_err(Into::into)
}
pub(crate) async fn must_read_gateway_details(
&self,
) -> Result<PersistedGatewayDetails, ClientStorageError> {
self.may_read_gateway_details()
.await?
.ok_or(ClientStorageError::GatewayDetailsNotInStorage)
}
async fn may_read_identity_keypair(
&self,
) -> Result<Option<identity::KeyPair>, ClientStorageError> {
self.inner
.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
)
.await
.map_err(Into::into)
}
async fn may_read_encryption_keypair(
&self,
) -> Result<Option<encryption::KeyPair>, ClientStorageError> {
self.inner
.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
)
.await
.map_err(Into::into)
}
async fn may_read_ack_key(&self) -> Result<Option<AckKey>, ClientStorageError> {
self.inner
.read_value(v1::KEYS_STORE, JsValue::from_str(v1::AES128CTR_ACK_KEY))
.await
.map_err(Into::into)
}
async fn may_read_gateway_shared_key(&self) -> Result<Option<SharedKeys>, ClientStorageError> {
self.inner
.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
)
.await
.map_err(Into::into)
}
async fn must_read_identity_keypair(&self) -> Result<identity::KeyPair, ClientStorageError> {
self.may_read_identity_keypair()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::ED25519_IDENTITY_KEYPAIR.to_string(),
})
}
async fn must_read_encryption_keypair(
&self,
) -> Result<encryption::KeyPair, ClientStorageError> {
self.may_read_encryption_keypair()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::X25519_ENCRYPTION_KEYPAIR.to_string(),
})
}
async fn must_read_ack_key(&self) -> Result<AckKey, ClientStorageError> {
self.may_read_ack_key()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::AES128CTR_ACK_KEY.to_string(),
})
}
async fn must_read_gateway_shared_key(&self) -> Result<SharedKeys, ClientStorageError> {
self.may_read_gateway_shared_key()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS.to_string(),
})
}
async fn store_identity_keypair(
&self,
keypair: &identity::KeyPair,
) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
keypair,
)
.await
.map_err(Into::into)
}
async fn store_encryption_keypair(
&self,
keypair: &encryption::KeyPair,
) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
keypair,
)
.await
.map_err(Into::into)
}
async fn store_ack_key(&self, key: &AckKey) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_ACK_KEY),
key,
)
.await
.map_err(Into::into)
}
async fn store_gateway_shared_key(&self, key: &SharedKeys) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
key,
)
.await
.map_err(Into::into)
}
pub(crate) async fn store_gateway_details(
&self,
gateway_endpoint: &PersistedGatewayDetails,
) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::CORE_STORE,
JsValue::from_str(v1::GATEWAY_DETAILS),
gateway_endpoint,
)
.await
.map_err(Into::into)
}
// TODO: persist client's config
#[allow(dead_code)]
pub(crate) async fn store_config(&self, config: &Config) -> Result<(), ClientStorageError> {
self.inner
.store_value(v1::CORE_STORE, JsValue::from_str(v1::CONFIG), config)
.await
.map_err(Into::into)
}
pub(crate) async fn has_full_gateway_info(&self) -> Result<bool, ClientStorageError> {
let has_keys = self.may_read_gateway_shared_key().await?.is_some();
let has_details = self.may_read_gateway_details().await?.is_some();
Ok(has_keys && has_details)
}
}
-267
View File
@@ -1,267 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::GatewayEndpointConfig;
use nym_crypto::asymmetric::{encryption, identity};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::{Layer, MixnodeConversionError};
use nym_topology::{gateway, mix, MixLayer, NymTopology};
use nym_validator_client::client::{IdentityKeyRef, MixId};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use thiserror::Error;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use wasm_utils::{console_log, simple_js_error};
#[derive(Debug, Error)]
pub enum WasmTopologyError {
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
InvalidMixLayer { value: u8 },
#[error(transparent)]
GatewayConversion(#[from] GatewayConversionError),
#[error(transparent)]
MixnodeConversion(#[from] MixnodeConversionError),
#[error("The provided mixnode map was malformed: {source}")]
MalformedMixnodeMap { source: serde_wasm_bindgen::Error },
#[error("The provided gateway list was malformed: {source}")]
MalformedGatewayList { source: serde_wasm_bindgen::Error },
}
impl From<WasmTopologyError> for JsValue {
fn from(value: WasmTopologyError) -> Self {
simple_js_error(value.to_string())
}
}
#[wasm_bindgen]
#[derive(Debug)]
pub struct WasmNymTopology {
inner: NymTopology,
}
#[wasm_bindgen]
impl WasmNymTopology {
#[wasm_bindgen(constructor)]
pub fn new(
// expected: BTreeMap<MixLayer, Vec<WasmMixNode>>,
// HashMap<MixLayer, Vec<WasmMixNode>> will also work because it has the same json representation
mixnodes: JsValue,
// expected: Vec<WasmGateway>
gateways: JsValue,
) -> Result<WasmNymTopology, WasmTopologyError> {
let mixnodes: BTreeMap<MixLayer, Vec<WasmMixNode>> =
serde_wasm_bindgen::from_value(mixnodes)
.map_err(|source| WasmTopologyError::MalformedMixnodeMap { source })?;
let gateways: Vec<WasmGateway> = serde_wasm_bindgen::from_value(gateways)
.map_err(|source| WasmTopologyError::MalformedGatewayList { source })?;
let mut converted_mixes = BTreeMap::new();
for (layer, nodes) in mixnodes {
let layer_nodes = nodes
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
converted_mixes.insert(layer, layer_nodes);
}
let gateways = gateways
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
Ok(WasmNymTopology {
inner: NymTopology::new(converted_mixes, gateways),
})
}
#[allow(dead_code)]
pub(crate) fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
}
pub(crate) fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
self.inner
.gateways()
.iter()
.any(|g| g.identity_key.to_base58_string() == gateway_id)
}
pub fn print(&self) {
if !self.inner.mixes().is_empty() {
console_log!("mixnodes:");
for (layer, nodes) in self.inner.mixes() {
console_log!("\tlayer {layer}:");
for node in nodes {
console_log!("\t\t{} - {}", node.mix_id, node.identity_key)
}
}
} else {
console_log!("NO MIXNODES")
}
if !self.inner.gateways().is_empty() {
console_log!("gateways:");
for gateway in self.inner.gateways() {
console_log!("\t{}", gateway.identity_key)
}
} else {
console_log!("NO GATEWAYS")
}
}
}
impl From<WasmNymTopology> for NymTopology {
fn from(value: WasmNymTopology) -> Self {
value.inner
}
}
impl From<NymTopology> for WasmNymTopology {
fn from(value: NymTopology) -> Self {
WasmNymTopology { inner: value }
}
}
#[wasm_bindgen]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WasmMixNode {
pub mix_id: MixId,
#[wasm_bindgen(getter_with_clone)]
pub owner: String,
#[wasm_bindgen(getter_with_clone)]
pub host: String,
pub mix_port: u16,
#[wasm_bindgen(getter_with_clone)]
pub identity_key: String,
#[wasm_bindgen(getter_with_clone)]
pub sphinx_key: String,
pub layer: MixLayer,
#[wasm_bindgen(getter_with_clone)]
pub version: String,
}
#[wasm_bindgen]
impl WasmMixNode {
#[wasm_bindgen(constructor)]
#[allow(clippy::too_many_arguments)]
pub fn new(
mix_id: MixId,
owner: String,
host: String,
mix_port: u16,
identity_key: String,
sphinx_key: String,
layer: MixLayer,
version: String,
) -> Self {
Self {
mix_id,
owner,
host,
mix_port,
identity_key,
sphinx_key,
layer,
version,
}
}
}
impl TryFrom<WasmMixNode> for mix::Node {
type Error = WasmTopologyError;
fn try_from(value: WasmMixNode) -> Result<Self, Self::Error> {
let host = mix::Node::parse_host(&value.host)?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = mix::Node::extract_mix_host(&host, value.mix_port)?;
Ok(mix::Node {
mix_id: value.mix_id,
owner: value.owner,
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(MixnodeConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(MixnodeConversionError::from)?,
layer: Layer::try_from(value.layer)
.map_err(|_| WasmTopologyError::InvalidMixLayer { value: value.layer })?,
version: value.version,
})
}
}
#[wasm_bindgen]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WasmGateway {
#[wasm_bindgen(getter_with_clone)]
pub owner: String,
#[wasm_bindgen(getter_with_clone)]
pub host: String,
pub mix_port: u16,
pub clients_port: u16,
#[wasm_bindgen(getter_with_clone)]
pub identity_key: String,
#[wasm_bindgen(getter_with_clone)]
pub sphinx_key: String,
#[wasm_bindgen(getter_with_clone)]
pub version: String,
}
#[wasm_bindgen]
impl WasmGateway {
#[wasm_bindgen(constructor)]
pub fn new(
owner: String,
host: String,
mix_port: u16,
clients_port: u16,
identity_key: String,
sphinx_key: String,
version: String,
) -> Self {
Self {
owner,
host,
mix_port,
clients_port,
identity_key,
sphinx_key,
version,
}
}
}
impl TryFrom<WasmGateway> for gateway::Node {
type Error = WasmTopologyError;
fn try_from(value: WasmGateway) -> Result<Self, Self::Error> {
let host = gateway::Node::parse_host(&value.host)?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = gateway::Node::extract_mix_host(&host, value.mix_port)?;
Ok(gateway::Node {
owner: value.owner,
host,
mix_host,
clients_port: value.clients_port,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(GatewayConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(GatewayConversionError::from)?,
version: value.version,
})
}
}
-35
View File
@@ -1,35 +0,0 @@
use nym_sphinx::addressing::clients::Recipient;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn validate_recipient(recipient: String) -> Result<(), JsError> {
match Recipient::try_from_base58_string(recipient) {
Ok(_) => Ok(()),
Err(e) => Err(JsError::new(format!("{}", e).as_str())),
}
}
#[cfg(test)]
mod tests {
use super::validate_recipient;
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn test_recipient_validation_ok() {
let res = validate_recipient("DyQmXnst5NGGjzUZxRC5Bjs5bd7CBF3xMpsSmbRiizr2.GH6YTBP2NXU3AVqd8WjiTMVyeMjunXMEsp7gVCMEJqpD@336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9".to_string());
assert!(res.is_ok())
}
#[wasm_bindgen_test]
fn test_recipient_validation_fails() {
assert!(validate_recipient(" DyQmXnst5NGGjzUZxRC5Bjs5bd7CBF3xMpsSmbRiizr2.GH6YTBP2NXU3AVqd8WjiTMVyeMjunXMEsp7gVCMEJqpD@336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9".to_string()).is_err());
assert!(validate_recipient(
"DyQmXnst5NGGjzUZxRC5BjbRiizr2.GH6YTBP2NXU3AVqd8WD@336yuXAeGEgedRfqTJZQH1bHv1SjCZYarc9"
.to_string()
)
.is_err());
assert!(validate_recipient("🙀🙀🙀🙀".to_string()).is_err());
assert!(validate_recipient("".to_string()).is_err());
assert!(validate_recipient(" ".to_string()).is_err());
}
}
-13
View File
@@ -1,13 +0,0 @@
//! Test suite for the Web and headless browsers.
#![cfg(target_arch = "wasm32")]
extern crate wasm_bindgen_test;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn pass() {
assert_eq!(1 + 1, 2);
}
+10 -10
View File
@@ -22,11 +22,11 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sha2 = "0.10.6"
tap = "1.0.1"
thiserror = "1.0.34"
time = "0.3.17"
tokio = { version = "1.24.1", features = ["macros"]}
tungstenite = { version = "0.13.0", default-features = false }
thiserror = { workspace = true }
url = { workspace = true, features = ["serde"] }
tungstenite = { version = "0.13.0", default-features = false }
tokio = { workspace = true, features = ["macros"]}
time = "0.3.17"
zeroize = { workspace = true }
# internal
@@ -40,7 +40,7 @@ nym-gateway-requests = { path = "../../gateway/gateway-requests" }
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
nym-sphinx = { path = "../nymsphinx" }
nym-pemstore = { path = "../pemstore" }
nym-topology = { path = "../topology" }
nym-topology = { path = "../topology", features = ["serializable"] }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
nym-task = { path = "../task" }
nym-credential-storage = { path = "../credential-storage" }
@@ -51,7 +51,7 @@ version = "0.1.11"
features = ["time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
version = "1.24.1"
workspace = true
features = ["time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
@@ -63,10 +63,10 @@ features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
optional = true
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
version = "0.4"
workspace = true
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2.83"
workspace = true
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
workspace = true
@@ -77,7 +77,7 @@ version = "0.2.4"
features = ["futures"]
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
path = "../wasm-utils"
path = "../wasm/utils"
features = ["websocket"]
[target."cfg(target_arch = \"wasm32\")".dependencies.time]
@@ -88,7 +88,7 @@ features = ["wasm-bindgen"]
tempfile = "3.1.0"
[build-dependencies]
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
[features]
@@ -44,7 +44,9 @@ use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::HardcodedTopologyProvider;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use std::path::Path;
use std::sync::Arc;
use url::Url;
@@ -178,11 +180,13 @@ where
}
}
#[must_use]
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
self.setup_method = setup;
self
}
#[must_use]
pub fn with_topology_provider(
mut self,
provider: Box<dyn TopologyProvider + Send + Sync>,
@@ -191,6 +195,15 @@ where
self
}
pub fn with_stored_topology<P: AsRef<Path>>(
mut self,
file: P,
) -> Result<Self, ClientCoreError> {
self.custom_topology_provider =
Some(Box::new(HardcodedTopologyProvider::new_from_file(file)?));
Ok(self)
}
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
// because it relies on the crypto keys being already loaded
fn mix_address(details: &InitialisationDetails) -> Recipient {
@@ -224,6 +224,6 @@ impl RealMessagesController<OsRng> {
debug!("The reply controller has finished execution!");
});
// ack_control.start_with_shutdown(shutdown, packet_type);
ack_control.start_with_shutdown(shutdown, packet_type);
}
}
@@ -6,7 +6,7 @@ use async_trait::async_trait;
use std::error::Error;
use thiserror::Error;
#[cfg(target_arch = "wasm32")]
// TODO: this should now live inside our wasm/client-core
pub mod browser_backend;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
@@ -3,6 +3,7 @@
use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
use crate::config::disk_persistence::{CommonClientPaths, DEFAULT_GATEWAY_DETAILS_FILENAME};
use crate::error::ClientCoreError;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
@@ -15,16 +16,17 @@ pub struct CommonClientPathsV1_1_20_2 {
}
impl CommonClientPathsV1_1_20_2 {
pub fn upgrade_default(self) -> CommonClientPaths {
let data_dir = self
.reply_surb_database
.parent()
.expect("client paths upgrade failure");
CommonClientPaths {
pub fn upgrade_default(self) -> Result<CommonClientPaths, ClientCoreError> {
let data_dir = self.reply_surb_database.parent().ok_or_else(|| {
ClientCoreError::UnableToUpgradeConfigFile {
new_version: "1.1.20-2".to_string(),
}
})?;
Ok(CommonClientPaths {
keys: self.keys,
gateway_details: data_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME),
credentials_database: self.credentials_database,
reply_surb_database: self.reply_surb_database,
}
})
}
}
+10 -2
View File
@@ -69,7 +69,11 @@ pub struct Config {
}
impl Config {
pub fn new<S: Into<String>>(id: S, version: S) -> Self {
pub fn new<S1, S2>(id: S1, version: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
Config {
client: Client::new_default(id, version),
debug: Default::default(),
@@ -287,7 +291,11 @@ pub struct Client {
}
impl Client {
pub fn new_default<S: Into<String>>(id: S, version: S) -> Self {
pub fn new_default<S1, S2>(id: S1, version: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
let network = NymNetworkDetails::new_mainnet();
let nyxd_urls = network
.endpoints
+3
View File
@@ -119,6 +119,9 @@ pub enum ClientCoreError {
#[error("the provided gateway details (for gateway {gateway_id}) do not correspond to the shared keys")]
MismatchedGatewayDetails { gateway_id: String },
#[error("unable to upgrade config file to `{new_version}`")]
UnableToUpgradeConfigFile { new_version: String },
}
/// Set of messages that the client can send to listeners via the task manager
+3
View File
@@ -106,14 +106,17 @@ pub enum GatewaySetup {
/// Should the new gateway be selected based on latency.
by_latency: bool,
},
Specified {
/// Identity key of the gateway we want to try to use.
gateway_identity: IdentityKey,
},
Predefined {
/// Full gateway configuration
details: PersistedGatewayDetails,
},
ReuseConnection {
/// The authenticated ephemeral client that was created during `init`
authenticated_ephemeral_client: GatewayClient<InitOnly>,
+8 -5
View File
@@ -36,7 +36,7 @@ default-features = false
# non-wasm-only dependencies
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
version = "1.24.1"
workspace = true
features = ["macros", "rt", "net", "sync", "time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
@@ -48,15 +48,18 @@ version = "0.14"
# wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2"
workspace = true
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
version = "0.4"
workspace = true
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
path = "../../wasm-utils"
path = "../../wasm/utils"
features = ["websocket"]
[target."cfg(target_arch = \"wasm32\")".dependencies.gloo-utils]
workspace = true
# only import it in wasm. Prefer proper tokio timer in non-wasm
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
workspace = true
@@ -68,7 +71,7 @@ features = ["tokio"]
# containing javascript (such as a web browser or node.js).
# refer to https://docs.rs/getrandom/0.2.2/getrandom/#webassembly-support for more information
[target."cfg(target_arch = \"wasm32\")".dependencies.getrandom]
version = "0.2"
workspace = true
features = ["js"]
[dev-dependencies]
@@ -141,8 +141,8 @@ impl<C, St> GatewayClient<C, St> {
#[cfg(target_arch = "wasm32")]
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
SocketState::Available(mut socket) => {
(*socket).close(None).await;
SocketState::Available(socket) => {
(*socket).close(None, None).await?;
Ok(())
}
SocketState::PartiallyDelegated(_) => {
@@ -175,7 +175,9 @@ impl<C, St> GatewayClient<C, St> {
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
let ws_stream = match JSWebsocket::new(&self.gateway_address) {
Ok(ws_stream) => ws_stream,
Err(e) => return Err(GatewayClientError::NetworkErrorWasm(e)),
Err(e) => {
return Err(GatewayClientError::NetworkErrorWasm(e));
}
};
self.connection = SocketState::Available(Box::new(ws_stream));
@@ -1,12 +1,12 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(target_arch = "wasm32")]
use gloo_utils::errors::JsError;
use nym_gateway_requests::registration::handshake::error::HandshakeError;
use std::io;
use thiserror::Error;
use tungstenite::Error as WsError;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsValue;
#[derive(Debug, Error)]
pub enum GatewayClientError {
@@ -19,10 +19,9 @@ pub enum GatewayClientError {
#[error("There was a network error - {0}")]
NetworkError(#[from] WsError),
// TODO: see if `JsValue` is a reasonable type for this
#[cfg(target_arch = "wasm32")]
#[error("There was a network error")]
NetworkErrorWasm(JsValue),
NetworkErrorWasm(#[from] JsError),
#[error("Invalid URL - {0}")]
InvalidURL(String),
@@ -102,12 +102,12 @@ impl PacketRouter {
}
}
// if !received_acks.is_empty() {
// trace!("routing acks");
// if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
// error!("failed to send ack: {err}");
// };
// }
if !received_acks.is_empty() {
trace!("routing acks");
if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
error!("failed to send ack: {err}");
};
}
Ok(())
}
}
@@ -144,7 +144,7 @@ pub struct PendingIntervalEventData {
#[cw_serde]
pub enum PendingIntervalEventKind {
/// Request to update cost parameters of given mixnode.
#[serde(alias = "PendingIntervalEventKind")]
#[serde(alias = "ChangeMixCostParams")]
ChangeMixCostParams {
/// The id of the mixnode that will have its cost parameters updated.
mix_id: MixId,
@@ -5,9 +5,10 @@ use crate::backends::memory::CoconutCredentialManager;
use crate::error::StorageError;
use crate::models::CoconutCredential;
use crate::storage::Storage;
use async_trait::async_trait;
pub type EphemeralCredentialStorage = EphemeralStorage;
// note that clone here is fine as upon cloning the same underlying pool will be used
#[derive(Clone)]
pub struct EphemeralStorage {
+1 -2
View File
@@ -242,8 +242,7 @@ impl PrivateKey {
/// Signs text with the provided Ed25519 private key, returning a base58 signature
pub fn sign_text(&self, text: &str) -> String {
let signature_bytes = self.sign(text.as_ref()).to_bytes();
let signature = bs58::encode(signature_bytes).into_string();
signature
bs58::encode(signature_bytes).into_string()
}
}
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "nym-http-requests"
version = "0.1.0"
description = "Helper library for sending HTTP requesters over the Nym mixnet"
edition = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
[dependencies]
nym-socks5-requests = { path = "../socks5/requests" }
nym-ordered-buffer = { path = "../socks5/ordered-buffer" }
nym-service-providers-common = { path = "../../service-providers/common" }
bytecodec = "0.4.15"
httpcodec = "0.2.3"
bytes = "1"
http = "0.2.9"
thiserror = "1"
url = "2"
+23
View File
@@ -0,0 +1,23 @@
use std::io;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MixHttpRequestError {
#[error("invalid Socks5 response")]
InvalidSocks5Response,
#[error("the received Socks5 response was empty")]
EmptySocks5Response,
#[error("bytecodec Error: {0}")]
ByteCodecError(#[from] bytecodec::Error),
#[error("Url parse error: {0}")]
UrlParseError(#[from] url::ParseError),
#[error("could not resolve socket address from the provided URL")]
SocketAddrResolveError {
#[source]
source: io::Error,
},
}
+63
View File
@@ -0,0 +1,63 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bytecodec::bytes::BytesEncoder;
use bytecodec::io::IoEncodeExt;
use bytecodec::Encode;
use httpcodec::{BodyEncoder, Request, RequestEncoder};
pub mod error;
pub mod socks;
pub fn encode_http_request_as_string(
request: Request<Vec<u8>>,
) -> Result<String, error::MixHttpRequestError> {
// Encode HTTP request as bytes
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
encoder.start_encoding(request)?;
let mut buf = Vec::new();
encoder.encode_all(&mut buf)?;
Ok(String::from_utf8_lossy(&buf).to_string())
}
#[cfg(test)]
mod http_requests_tests {
use super::*;
use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
fn create_http_get_request() -> Request<Vec<u8>> {
let mut request = Request::new(
Method::new("GET").unwrap(),
RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
HttpVersion::V1_1,
b"".to_vec(),
);
let mut headers = request.header_mut();
headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
request
}
#[test]
fn http_request_ok() {
// Encode HTTP request as bytes
let request = create_http_get_request();
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
encoder.start_encoding(request).unwrap();
let mut buf = Vec::new();
encoder.encode_all(&mut buf).unwrap();
let body_as_string = String::from_utf8(buf).unwrap();
// replace newlines with \r\n
let expected = r"GET /.wellknown/wallet/validators.json HTTP/1.1
Host: nymtech.net
Content-Length: 0
"
.replace('\n', "\r\n");
assert_eq!(expected, body_as_string);
}
}
+214
View File
@@ -0,0 +1,214 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error;
use bytecodec::bytes::BytesEncoder;
use bytecodec::bytes::RemainingBytesDecoder;
use bytecodec::io::IoEncodeExt;
use bytecodec::{DecodeExt, Encode};
use httpcodec::{BodyDecoder, ResponseDecoder};
use httpcodec::{BodyEncoder, Request, RequestEncoder};
use nym_service_providers_common::interface::ProviderInterfaceVersion;
use nym_socks5_requests::{SocketData, Socks5ProtocolVersion, Socks5ProviderRequest};
pub fn encode_http_request_as_socks_send_request(
provider_interface: ProviderInterfaceVersion,
socks5_protocol: Socks5ProtocolVersion,
conn_id: u64,
request: Request<Vec<u8>>,
seq: Option<u64>,
local_closed: bool,
) -> Result<nym_socks5_requests::Socks5ProviderRequest, error::MixHttpRequestError> {
// Encode HTTP request as bytes
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
encoder.start_encoding(request)?;
let mut buf = Vec::new();
encoder.encode_all(&mut buf)?;
// Wrap it as SOCKS send request
let request_content = nym_socks5_requests::request::Socks5Request::new_send(
socks5_protocol,
SocketData::new(seq.unwrap_or_default(), conn_id, local_closed, buf),
);
// and wrap it in provider request
Ok(Socks5ProviderRequest::new_provider_data(
provider_interface,
request_content,
))
}
#[derive(Debug)]
pub struct MixHttpResponse {
// pub connection_id: u64,
// #[deprecated]
// pub is_closed: bool,
pub http_response: httpcodec::Response<Vec<u8>>,
// #[deprecated]
// pub seq: u64,
}
impl MixHttpResponse {
pub fn try_from_bytes(b: &[u8]) -> Result<MixHttpResponse, error::MixHttpRequestError> {
if b.is_empty() {
Err(error::MixHttpRequestError::EmptySocks5Response)
} else {
let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
let http_response = decoder.decode_from_bytes(b)?;
Ok(MixHttpResponse { http_response })
}
}
}
// impl TryFrom<Socks5Response> for MixHttpResponse {
// type Error = error::MixHttpRequestError;
//
// fn try_from(value: Socks5Response) -> Result<Self, Self::Error> {
// if let Socks5ResponseContent::NetworkData { content } = value.content {
// content.try_into()
// } else {
// Err(error::MixHttpRequestError::InvalidSocks5Response)
// }
// }
// }
//
// impl TryFrom<SocketData> for MixHttpResponse {
// type Error = error::MixHttpRequestError;
//
// fn try_from(value: SocketData) -> Result<Self, Self::Error> {
// if value.data.is_empty() {
// Err(error::MixHttpRequestError::EmptySocks5Response)
// } else {
// let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
// let http_response = decoder.decode_from_bytes(value.data.as_ref())?;
//
// Ok(MixHttpResponse {
// connection_id: value.header.connection_id,
// is_closed: value.header.local_socket_closed,
// http_response,
// seq: value.header.seq,
// })
// }
// }
// }
// pub fn decode_socks_response_as_http_response(
// socks5_response: Socks5Response,
// ) -> Result<MixHttpResponse, error::MixHttpRequestError> {
// socks5_response.try_into()
// }
//
// #[cfg(test)]
// mod http_requests_tests {
// use super::*;
// use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
// use nym_service_providers_common::interface::Serializable;
// use nym_socks5_requests::Socks5Response;
//
// fn create_http_get_request() -> Request<Vec<u8>> {
// let mut request = Request::new(
// Method::new("GET").unwrap(),
// RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
// HttpVersion::V1_1,
// b"".to_vec(),
// );
// let mut headers = request.header_mut();
// headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
//
// request
// }
//
// fn create_socks5_request_buffer() -> Vec<u8> {
// let request = create_http_get_request();
// let socks5_request = encode_http_request_as_socks_send_request(
// ProviderInterfaceVersion::new_current(),
// Socks5ProtocolVersion::new_current(),
// 99u64,
// request,
// Some(42u64),
// true,
// )
// .unwrap();
// socks5_request.into_bytes()
// }
//
// #[test]
// fn request_http_request_content_ok() {
// let buffer = create_socks5_request_buffer();
//
// // HTTP request string content is as expected
// assert_eq!(
// [71u8, 69u8, 84u8, 32u8, 47u8, 46u8, 119u8, 101u8],
// buffer[19..27]
// );
// }
//
// /// This test will fail if the framing of the request buffer changes, e.g. when OrderedMessage
// /// changes to have the `index` value as a field, instead of packed with the `data`
// #[test]
// fn request_size_as_expected_ok() {
// let buffer = create_socks5_request_buffer();
// // println!("{:?}", buffer) // uncomment and run `cargo test -- --nocapture` to view
//
// assert_eq!(108, buffer.len()); // version set to SOCKS5
// }
//
// #[test]
// fn request_socks5_headers_ok() {
// let buffer = create_socks5_request_buffer();
//
// assert_eq!(5u8, buffer[0]); // version set to SOCKS5
// assert_eq!(1u8, buffer[1]); // type is SEND
// assert_eq!(99u8, buffer[9]); // ConnectionId is correct
// assert_eq!(1u8, buffer[10]); // local_closed is true
// }
//
// #[test]
// fn request_ordered_message_ok() {
// let buffer = create_socks5_request_buffer();
//
// // OrderedMessage index is correct
// assert_eq!(42u8, buffer[18]);
// }
//
// fn create_socks_response() -> Socks5Response {
// // HTTP response is just a string
// let http_response_string = "HTTP/1.1 200 OK\r\nServer: foo/0.0.1\r\n\r\n";
//
// let data = http_response_string.as_bytes().to_vec();
//
// // wrap in `NetworkData`, then Socks5Response
// Socks5Response::new(
// Socks5ProtocolVersion::new_current(),
// Socks5ResponseContent::NetworkData {
// content: SocketData::new(42, 99u64, false, data),
// },
// )
// }
//
// /// This test will fail is anything in the framing of the socks5_response byte
// /// representation changes
// #[test]
// fn response_byte_size_is_as_expected() {
// let socks5_response = create_socks_response();
// let buf = socks5_response.into_bytes();
//
// assert_eq!(57, buf.len());
// }
//
// #[test]
// fn response_parses() {
// unimplemented!()
// // let socks5_response = create_socks_response();
// // let response = decode_socks_response_as_http_response(socks5_response).unwrap();
// //
// // assert_eq!(42u64, response.seq); // OrderedMessage index as expected
// // assert_eq!(HttpVersion::V1_1, response.http_response.http_version());
// // assert_eq!(200u16, response.http_response.status_code().as_u16());
// // assert_eq!(
// // "foo/0.0.1",
// // response.http_response.header().get_field("Server").unwrap()
// // );
// }
// }
+3 -3
View File
@@ -197,12 +197,12 @@ impl VerlocMeasurer {
config.packet_timeout,
config.connection_timeout,
config.delay_between_packets,
shutdown_listener.clone(),
shutdown_listener.clone().named("VerlocPacketSender"),
)),
packet_listener: Arc::new(PacketListener::new(
config.listening_address,
Arc::clone(&identity),
shutdown_listener.clone(),
shutdown_listener.clone().named("VerlocPacketListener"),
)),
shutdown_listener,
currently_used_api: 0,
@@ -241,7 +241,7 @@ impl VerlocMeasurer {
return MeasurementOutcome::Done;
}
let mut shutdown_listener = self.shutdown_listener.clone();
let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement");
shutdown_listener.mark_as_success();
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
+1 -1
View File
@@ -83,7 +83,7 @@ impl PacketSender {
self: Arc<Self>,
tested_node: TestedNode,
) -> Result<Measurement, RttError> {
let mut shutdown_listener = self.shutdown_listener.clone();
let mut shutdown_listener = self.shutdown_listener.fork(tested_node.address.to_string());
shutdown_listener.mark_as_success();
let mut conn = match tokio::time::timeout(
+1 -1
View File
@@ -27,4 +27,4 @@ workspace = true
## wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
path = "../wasm-utils"
path = "../wasm/utils"
+3
View File
@@ -9,6 +9,9 @@ pub mod receiver;
pub mod tester;
pub use message::{Empty, TestMessage};
pub use nym_sphinx::{
chunking::fragment::FragmentIdentifier, params::PacketSize, preparer::PreparedFragment,
};
pub use tester::NodeTester;
// it feels wrong to redefine it, but I don't want to import the whole of contract commons just for this one type
+1
View File
@@ -15,3 +15,4 @@ thiserror = "1.0.37"
[dev-dependencies]
rand = "0.7"
nym-crypto = { path = "../../crypto", features = ["rand"] }
+8 -1
View File
@@ -39,7 +39,7 @@ pub enum RecipientFormattingError {
}
// TODO: this should a different home... somewhere, but where?
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Recipient {
client_identity: ClientIdentity,
client_encryption_key: ClientEncryptionKey,
@@ -226,6 +226,13 @@ impl std::fmt::Display for Recipient {
}
}
impl std::fmt::Debug for Recipient {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// use the Display implementation
<Self as std::fmt::Display>::fmt(self, f)
}
}
impl FromStr for Recipient {
type Err = RecipientFormattingError;
+4 -4
View File
@@ -243,7 +243,7 @@ mod message_receiver {
)
.unwrap(),
layer: Layer::One,
version: "0.8.0-dev".to_string(),
version: "0.8.0-dev".into(),
}],
);
@@ -263,7 +263,7 @@ mod message_receiver {
)
.unwrap(),
layer: Layer::Two,
version: "0.8.0-dev".to_string(),
version: "0.8.0-dev".into(),
}],
);
@@ -283,7 +283,7 @@ mod message_receiver {
)
.unwrap(),
layer: Layer::Three,
version: "0.8.0-dev".to_string(),
version: "0.8.0-dev".into(),
}],
);
@@ -303,7 +303,7 @@ mod message_receiver {
"EB42xvMFMD5rUCstE2CDazgQQJ22zLv8SPm1Luxni44c",
)
.unwrap(),
version: "0.8.0-dev".to_string(),
version: "0.8.0-dev".into(),
}],
)
}
+11 -2
View File
@@ -26,6 +26,7 @@ use nym_sphinx::params::PacketType;
use nym_task::{TaskClient, TaskManager};
use std::error::Error;
use std::path::PathBuf;
pub mod config;
pub mod error;
@@ -57,6 +58,9 @@ pub struct NymClient<S> {
storage: S,
setup_method: GatewaySetup,
/// Optional path to a .json file containing standalone network details.
custom_mixnet: Option<PathBuf>,
}
impl<S> NymClient<S>
@@ -68,11 +72,12 @@ where
<S::GatewayDetailsStore as GatewayDetailsStore>::StorageError: Sync + Send,
<S::KeyStore as KeyStore>::StorageError: Send + Sync,
{
pub fn new(config: Config, storage: S) -> Self {
pub fn new(config: Config, storage: S, custom_mixnet: Option<PathBuf>) -> Self {
NymClient {
config,
storage,
setup_method: GatewaySetup::MustLoad,
custom_mixnet,
}
}
@@ -210,10 +215,14 @@ where
Some(default_query_dkg_client_from_config(&self.config.base))
};
let base_builder =
let mut base_builder =
BaseClientBuilder::new(&self.config.base, self.storage, dkg_query_client)
.with_gateway_setup(self.setup_method);
if let Some(custom_mixnet) = &self.custom_mixnet {
base_builder = base_builder.with_stored_topology(custom_mixnet)?;
}
let packet_type = self.config.base.debug.traffic.packet_type;
let mut started_client = base_builder.start_base().await?;
let self_address = started_client.address;
+11 -1
View File
@@ -78,6 +78,16 @@ impl OrderedMessageBuffer {
Ok(())
}
/// Checks whether the buffer contains enough contiguous regions to read until the specified target sequence.
pub fn can_read_until(&self, target: u64) -> bool {
for seq in self.next_sequence..=target {
if !self.messages.contains_key(&seq) {
return false;
}
}
true
}
/// Returns `Option<Vec<u8>>` where it's `Some(bytes)` if there is gapless
/// ordered data in the buffer, and `None` if the buffer is empty or has
/// gaps in the contained data.
@@ -110,7 +120,7 @@ impl OrderedMessageBuffer {
);
Some(ReadContiguousData {
data: contiguous_messages,
last_sequence: seq,
last_sequence: self.next_sequence - 1,
})
}
}
@@ -171,7 +171,7 @@ impl Controller {
if let Some(payload) = active_connection.read_from_buf() {
if let Some(closed_at_index) = active_connection.closed_at_index {
if payload.last_sequence > closed_at_index {
if payload.last_sequence >= closed_at_index {
active_connection.is_closed = true;
}
}
+15 -1
View File
@@ -9,6 +9,7 @@ use nym_service_providers_common::interface::{Serializable, ServiceProviderReque
use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError};
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt::{Debug, Formatter};
use tap::TapFallible;
use thiserror::Error;
@@ -75,7 +76,7 @@ impl RequestDeserializationError {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq)]
pub struct ConnectRequest {
// TODO: is connection_id redundant now?
pub conn_id: ConnectionId,
@@ -83,6 +84,19 @@ pub struct ConnectRequest {
pub return_address: Option<Recipient>,
}
impl Debug for ConnectRequest {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConnectRequest")
.field("conn_id", &self.conn_id)
.field("remote_addr", &self.remote_addr)
.field(
"return_address",
&self.return_address.map(|r| r.to_string()),
)
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SendRequest {
pub data: SocketData,
+1 -1
View File
@@ -64,7 +64,7 @@ pub enum ResponseDeserializationError {
},
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Socks5Response {
pub protocol_version: Socks5ProtocolVersion,
pub content: Socks5ResponseContent,
+115 -11
View File
@@ -5,6 +5,7 @@ use std::future::Future;
use std::{error::Error, time::Duration};
use futures::{future::pending, FutureExt, SinkExt, StreamExt};
use log::{log, Level};
use tokio::{
sync::{
mpsc,
@@ -23,10 +24,18 @@ pub type SentStatus = Box<dyn Error + Send + Sync>;
pub type StatusSender = futures::channel::mpsc::Sender<SentStatus>;
pub type StatusReceiver = futures::channel::mpsc::Receiver<SentStatus>;
fn try_recover_name(name: &Option<String>) -> String {
if let Some(name) = name {
name.clone()
} else {
"unknown".to_string()
}
}
#[derive(thiserror::Error, Debug)]
enum TaskError {
#[error("Task halted unexpectedly")]
UnexpectedHalt,
#[error("Task '{}' halted unexpectedly", try_recover_name(.shutdown_name))]
UnexpectedHalt { shutdown_name: Option<String> },
}
// TODO: possibly we should create a `Status` trait instead of reusing `Error`
@@ -40,6 +49,9 @@ pub enum TaskStatus {
/// shutdown. Keeps track of if task stop unexpectedly, such as in a panic.
#[derive(Debug)]
pub struct TaskManager {
// optional name assigned to the task manager that all subscribed task clients will inherit
name: Option<String>,
// These channels have the dual purpose of signalling it's time to shutdown, but also to keep
// track of which tasks we are still waiting for.
notify_tx: watch::Sender<()>,
@@ -72,6 +84,7 @@ impl Default for TaskManager {
// there is a listener.
let (task_status_tx, task_status_rx) = futures::channel::mpsc::channel(128);
Self {
name: None,
notify_tx,
notify_rx: Some(notify_rx),
shutdown_timer_secs: DEFAULT_SHUTDOWN_TIMER_SECS,
@@ -93,6 +106,12 @@ impl TaskManager {
}
}
#[must_use]
pub fn named<S: Into<String>>(mut self, name: S) -> Self {
self.name = Some(name.into());
self
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn catch_interrupt(mut self) -> Result<(), SentError> {
let res = crate::wait_for_signal_and_error(&mut self).await;
@@ -107,7 +126,7 @@ impl TaskManager {
}
pub fn subscribe(&self) -> TaskClient {
TaskClient::new(
let task_client = TaskClient::new(
self.notify_rx
.as_ref()
.expect("Unable to subscribe to shutdown notifier that is already shutdown")
@@ -115,7 +134,13 @@ impl TaskManager {
self.task_return_error_tx.clone(),
self.task_drop_tx.clone(),
self.task_status_tx.clone(),
)
);
if let Some(name) = &self.name {
task_client.named(format!("{name}-child"))
} else {
task_client
}
}
pub fn signal_shutdown(&self) -> Result<(), SendError<()>> {
@@ -207,8 +232,11 @@ impl TaskManager {
/// Listen for shutdown notifications, and can send error and status messages back to the
/// `TaskManager`
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct TaskClient {
// optional name assigned to the shutdown handle
name: Option<String>,
// If a shutdown notification has been registered
shutdown: bool,
@@ -229,7 +257,35 @@ pub struct TaskClient {
mode: ClientOperatingMode,
}
impl Clone for TaskClient {
fn clone(&self) -> Self {
// make sure to not accidentally overflow the stack if we keep cloning the handle
let name = if let Some(name) = &self.name {
if name != Self::OVERFLOW_NAME && name.len() < Self::MAX_NAME_LENGTH {
Some(format!("{name}-child"))
} else {
Some(Self::OVERFLOW_NAME.to_string())
}
} else {
None
};
TaskClient {
name,
shutdown: self.shutdown,
notify: self.notify.clone(),
return_error: self.return_error.clone(),
drop_error: self.drop_error.clone(),
status_msg: self.status_msg.clone(),
mode: self.mode.clone(),
}
}
}
impl TaskClient {
const MAX_NAME_LENGTH: usize = 128;
const OVERFLOW_NAME: &'static str = "reached maximum TaskClient children name depth";
#[cfg(not(target_arch = "wasm32"))]
const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(5);
@@ -240,6 +296,7 @@ impl TaskClient {
status_msg: StatusSender,
) -> TaskClient {
TaskClient {
name: None,
shutdown: false,
notify,
return_error,
@@ -249,6 +306,41 @@ impl TaskClient {
}
}
// TODO: not convinced about the name...
pub fn fork<S: Into<String>>(&self, child_suffix: S) -> Self {
let mut child = self.clone();
let suffix = child_suffix.into();
let child_name = if let Some(base) = &self.name {
format!("{base}-{suffix}")
} else {
format!("unknown-{suffix}")
};
child.name = Some(child_name);
child
}
// just a convenience wrapper for including the shutdown name when logging
// I really didn't want to create macros for that... because that seemed like an overkill.
// but I guess it would have resolved needing to call `format!` for additional msg arguments
fn log<S: Into<String>>(&self, level: Level, msg: S) {
let msg = msg.into();
let target = &if let Some(name) = &self.name {
format!("TaskClient-{name}")
} else {
"unnamed-TaskClient".to_string()
};
log!(target: target, level, "{msg}")
}
#[must_use]
pub fn named<S: Into<String>>(mut self, name: S) -> Self {
self.name = Some(name.into());
self
}
pub async fn run_future<Fut, T>(&mut self, fut: Fut) -> Option<T>
where
Fut: Future<Output = T>,
@@ -267,6 +359,7 @@ impl TaskClient {
let (task_drop_tx, _task_drop_rx) = mpsc::unbounded_channel();
let (task_status_tx, _task_status_rx) = futures::channel::mpsc::channel(128);
TaskClient {
name: None,
shutdown: false,
notify: notify_rx,
return_error: task_halt_tx,
@@ -310,6 +403,7 @@ impl TaskClient {
pub async fn recv_timeout(&mut self) {
if self.mode.is_dummy() {
#[cfg_attr(target_arch = "wasm32", allow(clippy::needless_return))]
return pending().await;
}
#[cfg(not(target_arch = "wasm32"))]
@@ -336,8 +430,9 @@ impl TaskClient {
has_changed
}
Err(err) => {
log::error!("Polling shutdown failed: {err}");
log::error!("Assuming this means we should shutdown...");
self.log(Level::Error, format!("Polling shutdown failed: {err}"));
self.log(Level::Error, "Assuming this means we should shutdown...");
true
}
}
@@ -354,9 +449,11 @@ impl TaskClient {
if self.mode.is_dummy() {
return;
}
log::trace!("Notifying we stopped: {err}");
self.log(Level::Trace, format!("Notifying we stopped: {err}"));
if self.return_error.send(err).is_err() {
log::error!("Failed to send back error message");
self.log(Level::Error, "failed to send back error message");
}
}
@@ -373,13 +470,20 @@ impl TaskClient {
impl Drop for TaskClient {
fn drop(&mut self) {
if !self.mode.should_signal_on_drop() {
self.log(Level::Debug, "the task client is getting dropped");
return;
} else {
self.log(Level::Info, "the task client is getting dropped");
}
if !self.is_shutdown_poll() {
log::trace!("Notifying stop on unexpected drop");
self.log(Level::Trace, "Notifying stop on unexpected drop");
// If we can't send, well then there is not much to do
self.drop_error
.send(Box::new(TaskError::UnexpectedHalt))
.send(Box::new(TaskError::UnexpectedHalt {
shutdown_name: self.name.clone(),
}))
.ok();
}
}
+18 -1
View File
@@ -17,6 +17,15 @@ log = { workspace = true }
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
thiserror = "1.0.37"
async-trait = { workspace = true, optional = true }
semver = "0.11"
# 'serializable' feature
serde = { workspace = true, features = ["derive"], optional = true }
serde_json = { workspace = true, optional = true }
# 'wasm-serde-types' feature
tsify = { workspace = true, features = ["js"], optional = true }
wasm-bindgen = { workspace = true, optional = true }
## internal
nym-crypto = { path = "../crypto", features = ["sphinx", "outfox"] }
@@ -26,6 +35,14 @@ nym-sphinx-types = { path = "../nymsphinx/types", features = ["sphinx", "outfox"
nym-sphinx-routing = { path = "../nymsphinx/routing" }
nym-bin-common = { path = "../bin-common" }
# 'serializable' feature
nym-config = { path = "../config", optional = true }
# 'wasm-serde-types' feature
wasm-utils = { path = "../wasm/utils", default-features = false, optional = true }
[features]
default = ["provider-trait"]
provider-trait = ["async-trait"]
provider-trait = ["async-trait"]
wasm-serde-types = ["tsify", "wasm-bindgen", "wasm-utils"]
serializable = ["serde", "nym-config", "serde_json"]
+5 -4
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{filter, NetworkAddress};
use crate::{filter, NetworkAddress, NodeVersion};
use nym_crypto::asymmetric::{encryption, identity};
use nym_mixnet_contract_common::GatewayBond;
use nym_sphinx_addressing::nodes::{NodeIdentity, NymNodeRoutingAddress};
@@ -38,7 +38,7 @@ pub struct Node {
pub clients_port: u16,
pub identity_key: identity::PublicKey,
pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519
pub version: String,
pub version: NodeVersion,
}
impl Node {
@@ -83,7 +83,8 @@ impl fmt::Display for Node {
impl filter::Versioned for Node {
fn version(&self) -> String {
self.version.clone()
// TODO: return semver instead
self.version.to_string()
}
}
@@ -114,7 +115,7 @@ impl<'a> TryFrom<&'a GatewayBond> for Node {
clients_port: bond.gateway.clients_port,
identity_key: identity::PublicKey::from_base58_string(&bond.gateway.identity_key)?,
sphinx_key: encryption::PublicKey::from_base58_string(&bond.gateway.sphinx_key)?,
version: bond.gateway.version.clone(),
version: bond.gateway.version.as_str().into(),
})
}
}
+74 -2
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::filter::VersionFilterable;
pub use error::NymTopologyError;
use log::warn;
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
@@ -16,6 +17,9 @@ use std::io;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::str::FromStr;
#[cfg(feature = "serializable")]
use ::serde::{Deserialize, Deserializer, Serialize, Serializer};
pub mod error;
pub mod filter;
pub mod gateway;
@@ -25,7 +29,44 @@ pub mod random_route_provider;
#[cfg(feature = "provider-trait")]
pub mod provider_trait;
pub use error::NymTopologyError;
#[cfg(feature = "serializable")]
pub(crate) mod serde;
#[cfg(feature = "serializable")]
pub use crate::serde::{SerializableNymTopology, SerializableTopologyError};
#[cfg(feature = "provider-trait")]
pub use provider_trait::{HardcodedTopologyProvider, TopologyProvider};
#[derive(Debug, Default, Clone)]
pub enum NodeVersion {
Explicit(semver::Version),
#[default]
Unknown,
}
// this is only implemented for backwards compatibility so we wouldn't need to change everything at once
// (also I intentionally implemented `ToString` as opposed to `Display`)
impl ToString for NodeVersion {
fn to_string(&self) -> String {
match self {
NodeVersion::Explicit(semver) => semver.to_string(),
NodeVersion::Unknown => String::new(),
}
}
}
// this is also for backwards compat.
impl<'a> From<&'a str> for NodeVersion {
fn from(value: &'a str) -> Self {
if let Ok(semver) = value.parse() {
NodeVersion::Explicit(semver)
} else {
NodeVersion::Unknown
}
}
}
#[derive(Debug, Clone)]
pub enum NetworkAddress {
@@ -78,6 +119,12 @@ impl NymTopology {
NymTopology { mixes, gateways }
}
#[cfg(feature = "serializable")]
pub fn new_from_file<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self> {
let file = std::fs::File::open(path)?;
serde_json::from_reader(file).map_err(Into::into)
}
pub fn from_detailed(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<GatewayBond>,
@@ -139,6 +186,10 @@ impl NymTopology {
&self.gateways
}
pub fn get_gateways(&self) -> Vec<gateway::Node> {
self.gateways.clone()
}
pub fn get_gateway(&self, gateway_identity: &NodeIdentity) -> Option<&gateway::Node> {
self.gateways
.iter()
@@ -318,6 +369,27 @@ impl NymTopology {
}
}
#[cfg(feature = "serializable")]
impl Serialize for NymTopology {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
crate::serde::SerializableNymTopology::from(self.clone()).serialize(serializer)
}
}
#[cfg(feature = "serializable")]
impl<'de> Deserialize<'de> for NymTopology {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let serializable = crate::serde::SerializableNymTopology::deserialize(deserializer)?;
serializable.try_into().map_err(::serde::de::Error::custom)
}
}
pub fn nym_topology_from_detailed(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<GatewayBond>,
@@ -390,7 +462,7 @@ mod converting_mixes_to_vec {
)
.unwrap(),
layer: Layer::One,
version: "0.x.0".to_string(),
version: "0.2.0".into(),
};
let node2 = mix::Node {
+5 -4
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{filter, NetworkAddress};
use crate::{filter, NetworkAddress, NodeVersion};
use nym_crypto::asymmetric::{encryption, identity};
pub use nym_mixnet_contract_common::Layer;
use nym_mixnet_contract_common::{MixId, MixNodeBond};
@@ -39,7 +39,7 @@ pub struct Node {
pub identity_key: identity::PublicKey,
pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519
pub layer: Layer,
pub version: String,
pub version: NodeVersion,
}
impl Node {
@@ -66,7 +66,8 @@ impl Node {
impl filter::Versioned for Node {
fn version(&self) -> String {
self.version.clone()
// TODO: return semver instead
self.version.to_string()
}
}
@@ -98,7 +99,7 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node {
identity_key: identity::PublicKey::from_base58_string(&bond.mix_node.identity_key)?,
sphinx_key: encryption::PublicKey::from_base58_string(&bond.mix_node.sphinx_key)?,
layer: bond.layer,
version: bond.mix_node.version.clone(),
version: bond.mix_node.version.as_str().into(),
})
}
}
+5
View File
@@ -22,6 +22,11 @@ pub struct HardcodedTopologyProvider {
}
impl HardcodedTopologyProvider {
#[cfg(feature = "serializable")]
pub fn new_from_file<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self> {
NymTopology::new_from_file(path).map(Self::new)
}
pub fn new(topology: NymTopology) -> Self {
HardcodedTopologyProvider { topology }
}
+238
View File
@@ -0,0 +1,238 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::gateway::GatewayConversionError;
use crate::mix::MixnodeConversionError;
use crate::{gateway, mix, MixLayer, NymTopology};
use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use nym_crypto::asymmetric::{encryption, identity};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use thiserror::Error;
#[cfg(feature = "wasm-serde-types")]
use tsify::Tsify;
#[cfg(feature = "wasm-serde-types")]
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
#[cfg(feature = "wasm-serde-types")]
use wasm_utils::error::simple_js_error;
#[derive(Debug, Error)]
pub enum SerializableTopologyError {
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
InvalidMixLayer { value: u8 },
#[error(transparent)]
GatewayConversion(#[from] GatewayConversionError),
#[error(transparent)]
MixnodeConversion(#[from] MixnodeConversionError),
#[error("The provided mixnode map was malformed: {msg}")]
MalformedMixnodeMap { msg: String },
#[error("The provided gateway list was malformed: {msg}")]
MalformedGatewayList { msg: String },
}
#[cfg(feature = "wasm-serde-types")]
impl From<SerializableTopologyError> for JsValue {
fn from(value: SerializableTopologyError) -> Self {
simple_js_error(value.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))]
#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct SerializableNymTopology {
pub mixnodes: BTreeMap<MixLayer, Vec<SerializableMixNode>>,
pub gateways: Vec<SerializableGateway>,
}
impl TryFrom<SerializableNymTopology> for NymTopology {
type Error = SerializableTopologyError;
fn try_from(value: SerializableNymTopology) -> Result<Self, Self::Error> {
let mut converted_mixes = BTreeMap::new();
for (layer, nodes) in value.mixnodes {
let layer_nodes = nodes
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
converted_mixes.insert(layer, layer_nodes);
}
let gateways = value
.gateways
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
Ok(NymTopology::new(converted_mixes, gateways))
}
}
impl From<NymTopology> for SerializableNymTopology {
fn from(value: NymTopology) -> Self {
SerializableNymTopology {
mixnodes: value
.mixes()
.iter()
.map(|(&l, nodes)| (l, nodes.iter().map(Into::into).collect()))
.collect(),
gateways: value.gateways().iter().map(Into::into).collect(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))]
#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct SerializableMixNode {
// this is a `MixId` but due to typescript issue, we're using u32 directly.
#[serde(alias = "mix_id")]
pub mix_id: u32,
pub owner: String,
pub host: String,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "mix_port")]
pub mix_port: Option<u16>,
#[serde(alias = "identity_key")]
pub identity_key: String,
#[serde(alias = "sphinx_key")]
pub sphinx_key: String,
// this is a `MixLayer` but due to typescript issue, we're using u8 directly.
pub layer: u8,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
pub version: Option<String>,
}
impl TryFrom<SerializableMixNode> for mix::Node {
type Error = SerializableTopologyError;
fn try_from(value: SerializableMixNode) -> Result<Self, Self::Error> {
let host = mix::Node::parse_host(&value.host)?;
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = mix::Node::extract_mix_host(&host, mix_port)?;
Ok(mix::Node {
mix_id: value.mix_id,
owner: value.owner,
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(MixnodeConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(MixnodeConversionError::from)?,
layer: mix::Layer::try_from(value.layer)
.map_err(|_| SerializableTopologyError::InvalidMixLayer { value: value.layer })?,
version,
})
}
}
impl<'a> From<&'a mix::Node> for SerializableMixNode {
fn from(value: &'a mix::Node) -> Self {
SerializableMixNode {
mix_id: value.mix_id,
owner: value.owner.clone(),
host: value.host.to_string(),
mix_port: Some(value.mix_host.port()),
identity_key: value.identity_key.to_base58_string(),
sphinx_key: value.sphinx_key.to_base58_string(),
layer: value.layer.into(),
version: Some(value.version.to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))]
#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct SerializableGateway {
pub owner: String,
pub host: String,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "mix_port")]
pub mix_port: Option<u16>,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "clients_port")]
pub clients_port: Option<u16>,
#[serde(alias = "identity_key")]
pub identity_key: String,
#[serde(alias = "sphinx_key")]
pub sphinx_key: String,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
pub version: Option<String>,
}
impl TryFrom<SerializableGateway> for gateway::Node {
type Error = SerializableTopologyError;
fn try_from(value: SerializableGateway) -> Result<Self, Self::Error> {
let host = gateway::Node::parse_host(&value.host)?;
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
let clients_port = value.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT);
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = gateway::Node::extract_mix_host(&host, mix_port)?;
Ok(gateway::Node {
owner: value.owner,
host,
mix_host,
clients_port,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(GatewayConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(GatewayConversionError::from)?,
version,
})
}
}
impl<'a> From<&'a gateway::Node> for SerializableGateway {
fn from(value: &'a gateway::Node) -> Self {
SerializableGateway {
owner: value.owner.clone(),
host: value.host.to_string(),
mix_port: Some(value.mix_host.port()),
clients_port: Some(value.clients_port),
identity_key: value.identity_key.to_base58_string(),
sphinx_key: value.sphinx_key.to_base58_string(),
version: Some(value.version.to_string()),
}
}
}
-63
View File
@@ -1,63 +0,0 @@
[package]
name = "wasm-utils"
version = "0.1.0"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = { workspace = true }
js-sys = "^0.3.51"
wasm-bindgen = "=0.2.83"
wasm-bindgen-futures = "0.4"
serde = { workspace = true, features = ["derive"], optional = true }
serde-wasm-bindgen = { version = "0.5.0", optional = true }
getrandom = { version="0.2", features=["js"], optional = true }
indexed_db_futures = { version = " 0.3.0", optional = true }
thiserror = { workspace = true, optional = true }
nym-store-cipher = { path = "../store-cipher", features = ["json"], optional = true }
# we don't want entire tokio-tungstenite, tungstenite itself is just fine - we just want message and error enums
[dependencies.tungstenite]
version = "0.13"
default-features = false
optional = true
[dependencies.web-sys]
version = "0.3"
optional = true
[features]
default = ["sleep"]
sleep = ["web-sys", "web-sys/Window"]
websocket = [
"getrandom",
"tungstenite",
"web-sys",
"web-sys/BinaryType",
"web-sys/Blob",
"web-sys/CloseEvent",
"web-sys/ErrorEvent",
"web-sys/FileReader",
"web-sys/MessageEvent",
"web-sys/ProgressEvent",
"web-sys/WebSocket",
]
crypto = [
"web-sys",
"web-sys/Crypto",
"web-sys/CryptoKey",
"web-sys/CryptoKeyPair",
"web-sys/SubtleCrypto",
"web-sys/Window",
"web-sys/WorkerGlobalScope",
]
storage = [
"indexed_db_futures",
"nym-store-cipher",
"serde",
"serde-wasm-bindgen",
"thiserror"
]
-289
View File
@@ -1,289 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::websocket::state::State;
use crate::{console_error, console_log};
use futures::{Sink, Stream};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::io;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll, Waker};
use tungstenite::{Error as WsError, Message as WsMessage}; // use tungstenite Message and Error types for easier compatibility with `ClientHandshake`
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::{CloseEvent, ErrorEvent, MessageEvent, WebSocket};
mod state;
// Unfortunately this can't be cleanly done with TryFrom/TryInto traits as both are foreign types
fn try_message_event_into_ws_message(msg_event: MessageEvent) -> Result<WsMessage, WsError> {
match msg_event.data() {
buf if buf.is_instance_of::<js_sys::ArrayBuffer>() => {
let array = js_sys::Uint8Array::new(&buf);
Ok(WsMessage::Binary(array.to_vec()))
}
blob if blob.is_instance_of::<web_sys::Blob>() => {
console_error!("received a blob on the websocket - ignoring it!");
// we really don't want to bother dealing with Blobs, because it requires juggling filereaders,
// having event handlers to see when they're done, etc.
// + we shouldn't even get one [a blob] to begin with
// considering that our binary mode is (should be) set to array buffer.
Err(WsError::Io(io::Error::from(io::ErrorKind::InvalidInput)))
}
text if text.is_string() => match text.as_string() {
Some(text) => Ok(WsMessage::Text(text)),
None => Err(WsError::Utf8),
},
// "received a websocket message that is neither a String, ArrayBuffer or a Blob"
_ => Err(WsError::Io(io::Error::from(io::ErrorKind::InvalidInput))),
}
}
// Safety: when compiled to wasm32 everything is going to be running on a single thread and so there
// is no shared memory right now.
//
// Eventually it should be made `Send` properly. Wakers should probably be replaced with AtomicWaker
// and the item queue put behind an Arc<Mutex<...>>.
// It might also be worth looking at what https://crates.io/crates/send_wrapper could provide.
// Because I'm not sure Mutex would solve the `Closure` issue. It's the problem for later.
//
// ************************************
// SUPER IMPORTANT TODO: ONCE WASM IN RUST MATURES AND BECOMES MULTI-THREADED THIS MIGHT
// LEAD TO RUNTIME MEMORY CORRUPTION!!
// ************************************
//
unsafe impl Send for JSWebsocket {}
#[derive(Debug)]
#[allow(clippy::upper_case_acronyms)]
pub struct JSWebsocket {
socket: web_sys::WebSocket,
message_queue: Rc<RefCell<VecDeque<Result<WsMessage, WsError>>>>,
/// Waker of a task wanting to read incoming messages.
stream_waker: Rc<RefCell<Option<Waker>>>,
/// Waker of a task wanting to write to the sink.
sink_waker: Rc<RefCell<Option<Waker>>>,
/// Waker of a sink wanting to close the connection.
close_waker: Rc<RefCell<Option<Waker>>>,
// The callback closures. We need to store them as they will invalidate their
// corresponding JS callback whenever they are dropped, so if we were to
// normally return from `new` then our registered closures will
// raise an exception when invoked.
_on_open: Closure<dyn FnMut(JsValue)>,
_on_error: Closure<dyn FnMut(ErrorEvent)>,
_on_close: Closure<dyn FnMut(CloseEvent)>,
_on_message: Closure<dyn FnMut(MessageEvent)>,
}
impl JSWebsocket {
pub fn new(url: &str) -> Result<Self, JsValue> {
let ws = WebSocket::new(url)?;
// we don't want to ever have to deal with blobs
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
let message_queue = Rc::new(RefCell::new(VecDeque::new()));
let message_queue_clone = Rc::clone(&message_queue);
let stream_waker: Rc<RefCell<Option<Waker>>> = Rc::new(RefCell::new(None));
let stream_waker_clone = Rc::clone(&stream_waker);
let stream_waker_clone2 = Rc::clone(&stream_waker);
let sink_waker: Rc<RefCell<Option<Waker>>> = Rc::new(RefCell::new(None));
let sink_waker_clone = Rc::clone(&sink_waker);
let sink_waker_clone2 = Rc::clone(&sink_waker);
let close_waker: Rc<RefCell<Option<Waker>>> = Rc::new(RefCell::new(None));
let close_waker_clone = Rc::clone(&close_waker);
let on_message = Closure::wrap(Box::new(move |msg_event| {
let ws_message = try_message_event_into_ws_message(msg_event);
message_queue_clone.borrow_mut().push_back(ws_message);
// if there is a task waiting for messages - wake the executor!
if let Some(waker) = stream_waker_clone.borrow_mut().take() {
waker.wake()
}
}) as Box<dyn FnMut(MessageEvent)>);
let url_clone = url.to_string();
let on_open = Closure::wrap(Box::new(move |_| {
// in case there was a sink send request made before connection was fully established
console_log!("Websocket to {:?} is now open!", url_clone);
// if there is a task waiting to write messages - wake the executor!
if let Some(waker) = sink_waker_clone.borrow_mut().take() {
waker.wake()
}
// no need to wake the stream_waker because we won't have any message to send
// immediately anyway. It only makes sense to wake it during on_message (if any)
}) as Box<dyn FnMut(JsValue)>);
let on_error = Closure::wrap(Box::new(move |e: ErrorEvent| {
console_error!("Websocket error event: {:?}", e);
}) as Box<dyn FnMut(ErrorEvent)>);
let on_close = Closure::wrap(Box::new(move |e: CloseEvent| {
console_log!("Websocket close event: {:?}", e);
// something was waiting for the close event!
if let Some(waker) = close_waker_clone.borrow_mut().take() {
waker.wake()
}
// TODO: are waking those sufficient to prevent memory leaks?
if let Some(waker) = stream_waker_clone2.borrow_mut().take() {
waker.wake()
}
if let Some(waker) = sink_waker_clone2.borrow_mut().take() {
waker.wake()
}
}) as Box<dyn FnMut(CloseEvent)>);
ws.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
ws.set_onerror(Some(on_error.as_ref().unchecked_ref()));
ws.set_onopen(Some(on_open.as_ref().unchecked_ref()));
ws.set_onclose(Some(on_close.as_ref().unchecked_ref()));
Ok(JSWebsocket {
socket: ws,
message_queue,
stream_waker,
sink_waker,
close_waker,
_on_open: on_open,
_on_error: on_error,
_on_close: on_close,
_on_message: on_message,
})
}
pub async fn close(&mut self, code: Option<u16>) {
if let Some(code) = code {
self.socket
.close_with_code(code)
.expect("failed to close the socket!");
} else {
self.socket.close().expect("failed to close the socket!");
}
}
fn state(&self) -> State {
self.socket.ready_state().into()
}
}
impl Drop for JSWebsocket {
fn drop(&mut self) {
match self.state() {
State::Closed | State::Closing => {} // no need to do anything here
_ => self
.socket
.close()
.expect("failed to close WebSocket during drop!"),
}
self.socket.set_onmessage(None);
self.socket.set_onerror(None);
self.socket.set_onopen(None);
self.socket.set_onclose(None);
}
}
impl Stream for JSWebsocket {
type Item = Result<WsMessage, WsError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
// if there's anything in the internal queue, keep returning that
let ws_message = self.message_queue.borrow_mut().pop_front();
match ws_message {
Some(message) => Poll::Ready(Some(message)),
None => {
// if connection is closed or closing it means no more useful messages will ever arrive
// and hence we should signal this.
match self.state() {
State::Closing | State::Closed => Poll::Ready(None),
State::Open | State::Connecting => {
// clone the waker to be able to notify the executor once we get a new message
*self.stream_waker.borrow_mut() = Some(cx.waker().clone());
Poll::Pending
}
}
}
}
}
}
impl Sink<WsMessage> for JSWebsocket {
type Error = WsError;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
match self.state() {
State::Connecting => {
// clone the waker to be able to notify the executor once we get connected
*self.sink_waker.borrow_mut() = Some(cx.waker().clone());
Poll::Pending
}
State::Open => Poll::Ready(Ok(())),
State::Closing | State::Closed => Poll::Ready(Err(WsError::AlreadyClosed)),
}
}
fn start_send(self: Pin<&mut Self>, item: WsMessage) -> Result<(), Self::Error> {
// the only possible errors, per https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send
// are `INVALID_STATE_ERR` which is when connection is not in open state
// and `SYNTAX_ERR` which is when data is a string that has unpaired surrogates. This one
// is essentially impossible to happen in rust (assuming wasm_bindgen has done its jobs
// correctly, but even if not, there's nothing we can do ourselves.
// hence we can map all errors to not open
match self.state() {
State::Open => match item {
WsMessage::Binary(data) => self.socket.send_with_u8_array(&data),
WsMessage::Text(text) => self.socket.send_with_str(&text),
_ => unreachable!("those are not even exposed by the web_sys API"),
}
.map_err(|_| WsError::Io(io::Error::from(io::ErrorKind::NotConnected))),
State::Closing | State::Closed => Err(WsError::AlreadyClosed),
State::Connecting => Err(WsError::Io(io::Error::from(io::ErrorKind::NotConnected))),
}
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
// TODO: can we/should we do anything more here?
Poll::Ready(Ok(()))
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
match self.state() {
State::Open | State::Connecting => {
// TODO: do we need to wait for closing event here?
*self.close_waker.borrow_mut() = Some(cx.waker().clone());
// close inner socket
Poll::Ready(self.socket.close().map_err(|_| todo!()))
}
// if we're already closed, nothing left to do!
State::Closed => Poll::Ready(Ok(())),
State::Closing => {
*self.close_waker.borrow_mut() = Some(cx.waker().clone());
// wait for the close event...
Poll::Pending
}
}
}
}
-27
View File
@@ -1,27 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use web_sys::WebSocket;
// convenience wrapper for state values provided by [`web_sys::WebSocket`]
/// The state values correspond to the `readyState` API of the `WebSocket`.
/// See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState) for more details.
#[repr(u16)]
pub(crate) enum State {
Connecting = 0,
Open = 1,
Closing = 2,
Closed = 3,
}
impl From<u16> for State {
fn from(state: u16) -> Self {
match state {
WebSocket::CONNECTING => State::Connecting,
WebSocket::OPEN => State::Open,
WebSocket::CLOSING => State::Closing,
WebSocket::CLOSED => State::Closed,
n => panic!("{n} is not a valid WebSocket state!"), // should we panic here or change it into `TryFrom` instead?
}
}
}
+46
View File
@@ -0,0 +1,46 @@
[package]
name = "wasm-client-core"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/nymtech/nym"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { workspace = true }
js-sys = { workspace = true }
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { workspace = true, features = ["derive"] }
serde-wasm-bindgen = { workspace = true }
thiserror = { workspace = true }
tsify = { workspace = true, features = ["js"] }
url = { workspace = true }
wasm-bindgen = { workspace = true }
wasm-bindgen-futures = { workspace = true }
zeroize = { workspace = true }
nym-bandwidth-controller = { path = "../../bandwidth-controller" }
nym-client-core = { path = "../../client-core", default-features = false, features = ["wasm"] }
nym-config = { path = "../../config" }
nym-credential-storage = { path = "../../credential-storage" }
nym-crypto = { path = "../../crypto", features = ["asymmetric", "serde"] }
nym-gateway-client = { path = "../../client-libs/gateway-client", default-features = false, features = ["wasm"] }
nym-sphinx = { path = "../../nymsphinx" }
nym-sphinx-acknowledgements = { path = "../../nymsphinx/acknowledgements", features = ["serde"]}
nym-task = { path = "../../task" }
nym-topology = { path = "../../topology", features = ["serializable", "wasm-serde-types"] }
nym-validator-client = { path = "../../client-libs/validator-client", default-features = false }
wasm-utils = { path = "../utils" }
wasm-storage = { path = "../storage" }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1", optional = true }
[features]
default = ["console_error_panic_hook"]
@@ -1,4 +1,4 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
@@ -6,342 +6,90 @@
// another issue due to #[wasm_bindgen] and `Copy` trait
#![allow(dropping_copy_types)]
use nym_client_core::config::{
Acknowledgements as ConfigAcknowledgements, Config as BaseClientConfig,
CoverTraffic as ConfigCoverTraffic, DebugConfig as ConfigDebug,
GatewayConnection as ConfigGatewayConnection, ReplySurbs as ConfigReplySurbs,
Topology as ConfigTopology, Traffic as ConfigTraffic,
};
use crate::error::WasmCoreError;
use nym_config::helpers::OptionalSet;
use nym_sphinx::params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
pub(crate) base: BaseClientConfig,
pub mod r#override;
pub use nym_client_core::config::{
Acknowledgements as ConfigAcknowledgements, Config as BaseClientConfig,
CoverTraffic as ConfigCoverTraffic, DebugConfig as ConfigDebug,
GatewayConnection as ConfigGatewayConnection, ReplySurbs as ConfigReplySurbs,
Topology as ConfigTopology, Traffic as ConfigTraffic,
};
pub fn new_base_client_config(
id: String,
version: String,
nym_api: Option<String>,
nyxd: Option<String>,
debug: Option<DebugWasm>,
) -> Result<BaseClientConfig, WasmCoreError> {
let nym_api_url = match nym_api {
Some(raw) => Some(
raw.parse()
.map_err(|source| WasmCoreError::MalformedUrl { raw, source })?,
),
None => None,
};
let nyxd_url = match nyxd {
Some(raw) => Some(
raw.parse()
.map_err(|source| WasmCoreError::MalformedUrl { raw, source })?,
),
None => None,
};
Ok(BaseClientConfig::new(id, version)
.with_optional(
BaseClientConfig::with_custom_nym_apis,
nym_api_url.map(|u| vec![u]),
)
.with_optional(
BaseClientConfig::with_custom_nyxd,
nyxd_url.map(|u| vec![u]),
)
.with_debug_config(debug.map(Into::into).unwrap_or_default()))
}
#[wasm_bindgen]
impl Config {
#[wasm_bindgen(constructor)]
pub fn new(id: String, validator_server: String, debug: Option<DebugWasm>) -> Self {
Config {
base: BaseClientConfig::new(id, env!("CARGO_PKG_VERSION").to_string())
.with_custom_nyxd(vec![validator_server
.parse()
.expect("provided url was malformed")])
.with_debug_config(debug.map(Into::into).unwrap_or_default()),
}
}
pub(crate) fn new_tester_config<S: Into<String>>(id: S) -> Self {
Config {
base: BaseClientConfig::new(id.into(), env!("CARGO_PKG_VERSION").to_string())
.with_disabled_credentials(true)
.with_disabled_cover_traffic(true)
.with_disabled_topology_refresh(true),
}
}
pub fn default_debug() -> DebugWasm {
ConfigDebug::default().into()
}
#[wasm_bindgen(js_name = defaultDebug)]
pub fn default_debug_obj() -> JsValue {
let dbg = default_debug();
serde_wasm_bindgen::to_value(&dbg)
.expect("our 'DebugWasm' struct should have had a correctly defined serde implementation")
}
/// A client configuration in which no cover traffic will be sent,
/// in either the main distribution or the secondary traffic stream.
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct TrafficWasm {
/// 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.
pub average_packet_delay_ms: u64,
/// 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.
pub message_sending_average_delay_ms: u64,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
pub use_extended_packet_size: bool,
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
pub use_outfox: bool,
pub fn no_cover_debug() -> DebugWasm {
let mut cfg = ConfigDebug::default();
cfg.traffic.disable_main_poisson_packet_distribution = true;
cfg.cover_traffic.disable_loop_cover_traffic_stream = true;
cfg.into()
}
impl From<TrafficWasm> for ConfigTraffic {
fn from(traffic: TrafficWasm) -> Self {
let use_extended_packet_size = traffic
.use_extended_packet_size
.then(|| PacketSize::ExtendedPacket32);
let packet_type = if traffic.use_outfox {
PacketType::Outfox
} else {
PacketType::Mix
};
ConfigTraffic {
average_packet_delay: Duration::from_millis(traffic.average_packet_delay_ms),
message_sending_average_delay: Duration::from_millis(
traffic.message_sending_average_delay_ms,
),
disable_main_poisson_packet_distribution: traffic
.disable_main_poisson_packet_distribution,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: use_extended_packet_size,
packet_type,
}
}
}
impl From<ConfigTraffic> for TrafficWasm {
fn from(traffic: ConfigTraffic) -> Self {
TrafficWasm {
average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u64,
message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis()
as u64,
disable_main_poisson_packet_distribution: traffic
.disable_main_poisson_packet_distribution,
use_extended_packet_size: traffic.secondary_packet_size.is_some(),
use_outfox: traffic.packet_type == PacketType::Outfox,
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct CoverTrafficWasm {
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
pub loop_cover_traffic_average_delay_ms: u64,
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
/// Only applicable if `secondary_packet_size` is enabled.
pub cover_traffic_primary_size_ratio: f64,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
pub disable_loop_cover_traffic_stream: bool,
}
impl From<CoverTrafficWasm> for ConfigCoverTraffic {
fn from(cover_traffic: CoverTrafficWasm) -> Self {
ConfigCoverTraffic {
loop_cover_traffic_average_delay: Duration::from_millis(
cover_traffic.loop_cover_traffic_average_delay_ms,
),
cover_traffic_primary_size_ratio: cover_traffic.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
}
}
}
impl From<ConfigCoverTraffic> for CoverTrafficWasm {
fn from(cover_traffic: ConfigCoverTraffic) -> Self {
CoverTrafficWasm {
loop_cover_traffic_average_delay_ms: cover_traffic
.loop_cover_traffic_average_delay
.as_millis() as u64,
cover_traffic_primary_size_ratio: cover_traffic.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct GatewayConnectionWasm {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
pub gateway_response_timeout_ms: u64,
}
impl From<GatewayConnectionWasm> for ConfigGatewayConnection {
fn from(gateway_connection: GatewayConnectionWasm) -> Self {
ConfigGatewayConnection {
gateway_response_timeout: Duration::from_millis(
gateway_connection.gateway_response_timeout_ms,
),
}
}
}
impl From<ConfigGatewayConnection> for GatewayConnectionWasm {
fn from(gateway_connection: ConfigGatewayConnection) -> Self {
GatewayConnectionWasm {
gateway_response_timeout_ms: gateway_connection.gateway_response_timeout.as_millis()
as u64,
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct AcknowledgementsWasm {
/// 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.
pub average_ack_delay_ms: u64,
/// 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.
pub ack_wait_multiplier: f64,
/// 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.
pub ack_wait_addition_ms: u64,
}
impl From<AcknowledgementsWasm> for ConfigAcknowledgements {
fn from(acknowledgements: AcknowledgementsWasm) -> Self {
ConfigAcknowledgements {
average_ack_delay: Duration::from_millis(acknowledgements.average_ack_delay_ms),
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
ack_wait_addition: Duration::from_millis(acknowledgements.ack_wait_addition_ms),
}
}
}
impl From<ConfigAcknowledgements> for AcknowledgementsWasm {
fn from(acknowledgements: ConfigAcknowledgements) -> Self {
AcknowledgementsWasm {
average_ack_delay_ms: acknowledgements.average_ack_delay.as_millis() as u64,
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
ack_wait_addition_ms: acknowledgements.ack_wait_addition.as_millis() as u64,
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct TopologyWasm {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
pub topology_refresh_rate_ms: u64,
/// 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.
pub topology_resolution_timeout_ms: u64,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
}
impl From<TopologyWasm> for ConfigTopology {
fn from(topology: TopologyWasm) -> Self {
ConfigTopology {
topology_refresh_rate: Duration::from_millis(topology.topology_refresh_rate_ms),
topology_resolution_timeout: Duration::from_millis(
topology.topology_resolution_timeout_ms,
),
disable_refreshing: topology.disable_refreshing,
topology_structure: Default::default(),
}
}
}
impl From<ConfigTopology> for TopologyWasm {
fn from(topology: ConfigTopology) -> Self {
TopologyWasm {
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64,
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64,
disable_refreshing: topology.disable_refreshing,
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct ReplySurbsWasm {
/// 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.
pub minimum_reply_surb_storage_threshold: usize,
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
pub maximum_reply_surb_storage_threshold: usize,
/// Defines the minimum number of reply surbs the client would request.
pub minimum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs the client would request.
pub maximum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
pub maximum_allowed_reply_surb_request_size: u32,
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
/// for more even though in theory they wouldn't need to.
pub maximum_reply_surb_rerequest_waiting_period_ms: u64,
/// Defines maximum amount of time the client is going to wait for reply surbs before
/// deciding it's never going to get them and would drop all pending messages
pub maximum_reply_surb_drop_waiting_period_ms: u64,
/// Defines maximum amount of time given reply surb is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
pub maximum_reply_surb_age_ms: u64,
/// Defines maximum amount of time given reply key is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
pub maximum_reply_key_age_ms: u64,
}
impl From<ReplySurbsWasm> for ConfigReplySurbs {
fn from(reply_surbs: ReplySurbsWasm) -> Self {
ConfigReplySurbs {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: reply_surbs
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: Duration::from_millis(
reply_surbs.maximum_reply_surb_rerequest_waiting_period_ms,
),
maximum_reply_surb_drop_waiting_period: Duration::from_millis(
reply_surbs.maximum_reply_surb_drop_waiting_period_ms,
),
maximum_reply_surb_age: Duration::from_millis(reply_surbs.maximum_reply_surb_age_ms),
maximum_reply_key_age: Duration::from_millis(reply_surbs.maximum_reply_key_age_ms),
}
}
}
impl From<ConfigReplySurbs> for ReplySurbsWasm {
fn from(reply_surbs: ConfigReplySurbs) -> Self {
ReplySurbsWasm {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: reply_surbs
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period_ms: reply_surbs
.maximum_reply_surb_rerequest_waiting_period
.as_millis() as u64,
maximum_reply_surb_drop_waiting_period_ms: reply_surbs
.maximum_reply_surb_drop_waiting_period
.as_millis() as u64,
maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u64,
maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u64,
}
}
#[wasm_bindgen(js_name = noCoverDebug)]
pub fn no_cover_debug_obj() -> JsValue {
let dbg = no_cover_debug();
serde_wasm_bindgen::to_value(&dbg)
.expect("our 'DebugWasm' struct should have had a correctly defined serde implementation")
}
// just a helper structure to more easily pass through the JS boundary
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
#[wasm_bindgen(inspectable)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DebugWasm {
/// Defines all configuration options related to traffic streams.
pub traffic: TrafficWasm,
@@ -362,6 +110,12 @@ pub struct DebugWasm {
pub reply_surbs: ReplySurbsWasm,
}
impl Default for DebugWasm {
fn default() -> Self {
ConfigDebug::default().into()
}
}
impl From<DebugWasm> for ConfigDebug {
fn from(debug: DebugWasm) -> Self {
ConfigDebug {
@@ -388,7 +142,340 @@ impl From<ConfigDebug> for DebugWasm {
}
}
#[wasm_bindgen]
pub fn default_debug() -> DebugWasm {
ConfigDebug::default().into()
#[wasm_bindgen(inspectable)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TrafficWasm {
/// 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.
pub average_packet_delay_ms: u32,
/// 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.
pub message_sending_average_delay_ms: u32,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
pub use_extended_packet_size: bool,
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
pub use_outfox: bool,
}
impl Default for TrafficWasm {
fn default() -> Self {
ConfigTraffic::default().into()
}
}
impl From<TrafficWasm> for ConfigTraffic {
fn from(traffic: TrafficWasm) -> Self {
let use_extended_packet_size = traffic
.use_extended_packet_size
.then_some(PacketSize::ExtendedPacket32);
let packet_type = if traffic.use_outfox {
PacketType::Outfox
} else {
PacketType::Mix
};
ConfigTraffic {
average_packet_delay: Duration::from_millis(traffic.average_packet_delay_ms as u64),
message_sending_average_delay: Duration::from_millis(
traffic.message_sending_average_delay_ms as u64,
),
disable_main_poisson_packet_distribution: traffic
.disable_main_poisson_packet_distribution,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: use_extended_packet_size,
packet_type,
}
}
}
impl From<ConfigTraffic> for TrafficWasm {
fn from(traffic: ConfigTraffic) -> Self {
TrafficWasm {
average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u32,
message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis()
as u32,
disable_main_poisson_packet_distribution: traffic
.disable_main_poisson_packet_distribution,
use_extended_packet_size: traffic.secondary_packet_size.is_some(),
use_outfox: traffic.packet_type == PacketType::Outfox,
}
}
}
#[wasm_bindgen(inspectable)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CoverTrafficWasm {
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
pub loop_cover_traffic_average_delay_ms: u32,
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
/// Only applicable if `secondary_packet_size` is enabled.
pub cover_traffic_primary_size_ratio: f64,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
pub disable_loop_cover_traffic_stream: bool,
}
impl Default for CoverTrafficWasm {
fn default() -> Self {
ConfigCoverTraffic::default().into()
}
}
impl From<CoverTrafficWasm> for ConfigCoverTraffic {
fn from(cover_traffic: CoverTrafficWasm) -> Self {
ConfigCoverTraffic {
loop_cover_traffic_average_delay: Duration::from_millis(
cover_traffic.loop_cover_traffic_average_delay_ms as u64,
),
cover_traffic_primary_size_ratio: cover_traffic.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
}
}
}
impl From<ConfigCoverTraffic> for CoverTrafficWasm {
fn from(cover_traffic: ConfigCoverTraffic) -> Self {
CoverTrafficWasm {
loop_cover_traffic_average_delay_ms: cover_traffic
.loop_cover_traffic_average_delay
.as_millis() as u32,
cover_traffic_primary_size_ratio: cover_traffic.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
}
}
}
#[wasm_bindgen(inspectable)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GatewayConnectionWasm {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
pub gateway_response_timeout_ms: u32,
}
impl Default for GatewayConnectionWasm {
fn default() -> Self {
ConfigGatewayConnection::default().into()
}
}
impl From<GatewayConnectionWasm> for ConfigGatewayConnection {
fn from(gateway_connection: GatewayConnectionWasm) -> Self {
ConfigGatewayConnection {
gateway_response_timeout: Duration::from_millis(
gateway_connection.gateway_response_timeout_ms as u64,
),
}
}
}
impl From<ConfigGatewayConnection> for GatewayConnectionWasm {
fn from(gateway_connection: ConfigGatewayConnection) -> Self {
GatewayConnectionWasm {
gateway_response_timeout_ms: gateway_connection.gateway_response_timeout.as_millis()
as u32,
}
}
}
#[wasm_bindgen(inspectable)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AcknowledgementsWasm {
/// 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.
pub average_ack_delay_ms: u32,
/// 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.
pub ack_wait_multiplier: f64,
/// 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.
pub ack_wait_addition_ms: u32,
}
impl Default for AcknowledgementsWasm {
fn default() -> Self {
ConfigAcknowledgements::default().into()
}
}
impl From<AcknowledgementsWasm> for ConfigAcknowledgements {
fn from(acknowledgements: AcknowledgementsWasm) -> Self {
ConfigAcknowledgements {
average_ack_delay: Duration::from_millis(acknowledgements.average_ack_delay_ms as u64),
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
ack_wait_addition: Duration::from_millis(acknowledgements.ack_wait_addition_ms as u64),
}
}
}
impl From<ConfigAcknowledgements> for AcknowledgementsWasm {
fn from(acknowledgements: ConfigAcknowledgements) -> Self {
AcknowledgementsWasm {
average_ack_delay_ms: acknowledgements.average_ack_delay.as_millis() as u32,
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
ack_wait_addition_ms: acknowledgements.ack_wait_addition.as_millis() as u32,
}
}
}
#[wasm_bindgen(inspectable)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TopologyWasm {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
pub topology_refresh_rate_ms: u32,
/// 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.
pub topology_resolution_timeout_ms: u32,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
}
impl Default for TopologyWasm {
fn default() -> Self {
ConfigTopology::default().into()
}
}
impl From<TopologyWasm> for ConfigTopology {
fn from(topology: TopologyWasm) -> Self {
ConfigTopology {
topology_refresh_rate: Duration::from_millis(topology.topology_refresh_rate_ms as u64),
topology_resolution_timeout: Duration::from_millis(
topology.topology_resolution_timeout_ms as u64,
),
disable_refreshing: topology.disable_refreshing,
topology_structure: Default::default(),
}
}
}
impl From<ConfigTopology> for TopologyWasm {
fn from(topology: ConfigTopology) -> Self {
TopologyWasm {
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u32,
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u32,
disable_refreshing: topology.disable_refreshing,
}
}
}
#[wasm_bindgen(inspectable)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReplySurbsWasm {
/// 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.
pub minimum_reply_surb_storage_threshold: usize,
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
pub maximum_reply_surb_storage_threshold: usize,
/// Defines the minimum number of reply surbs the client would request.
pub minimum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs the client would request.
pub maximum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
pub maximum_allowed_reply_surb_request_size: u32,
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
/// for more even though in theory they wouldn't need to.
pub maximum_reply_surb_rerequest_waiting_period_ms: u32,
/// Defines maximum amount of time the client is going to wait for reply surbs before
/// deciding it's never going to get them and would drop all pending messages
pub maximum_reply_surb_drop_waiting_period_ms: u32,
/// 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.
pub maximum_reply_surb_age_ms: u32,
/// Defines maximum amount of time given reply key is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
pub maximum_reply_key_age_ms: u32,
}
impl Default for ReplySurbsWasm {
fn default() -> Self {
ConfigReplySurbs::default().into()
}
}
impl From<ReplySurbsWasm> for ConfigReplySurbs {
fn from(reply_surbs: ReplySurbsWasm) -> Self {
ConfigReplySurbs {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: reply_surbs
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: Duration::from_millis(
reply_surbs.maximum_reply_surb_rerequest_waiting_period_ms as u64,
),
maximum_reply_surb_drop_waiting_period: Duration::from_millis(
reply_surbs.maximum_reply_surb_drop_waiting_period_ms as u64,
),
maximum_reply_surb_age: Duration::from_millis(
reply_surbs.maximum_reply_surb_age_ms as u64,
),
maximum_reply_key_age: Duration::from_millis(
reply_surbs.maximum_reply_key_age_ms as u64,
),
}
}
}
impl From<ConfigReplySurbs> for ReplySurbsWasm {
fn from(reply_surbs: ConfigReplySurbs) -> Self {
ReplySurbsWasm {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: reply_surbs
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period_ms: reply_surbs
.maximum_reply_surb_rerequest_waiting_period
.as_millis() as u32,
maximum_reply_surb_drop_waiting_period_ms: reply_surbs
.maximum_reply_surb_drop_waiting_period
.as_millis() as u32,
maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u32,
maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u32,
}
}
}
@@ -0,0 +1,334 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{
AcknowledgementsWasm, CoverTrafficWasm, DebugWasm, GatewayConnectionWasm, ReplySurbsWasm,
TopologyWasm, TrafficWasm,
};
use crate::config::ConfigDebug;
use serde::{Deserialize, Serialize};
use tsify::Tsify;
// just a helper structure to more easily pass through the JS boundary
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct DebugWasmOverride {
/// Defines all configuration options related to traffic streams.
#[tsify(optional)]
pub traffic: Option<TrafficWasmOverride>,
/// Defines all configuration options related to cover traffic stream(s).
#[tsify(optional)]
pub cover_traffic: Option<CoverTrafficWasmOverride>,
/// Defines all configuration options related to the gateway connection.
#[tsify(optional)]
pub gateway_connection: Option<GatewayConnectionWasmOverride>,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
#[tsify(optional)]
pub acknowledgements: Option<AcknowledgementsWasmOverride>,
/// Defines all configuration options related topology, such as refresh rates or timeouts.
#[tsify(optional)]
pub topology: Option<TopologyWasmOverride>,
/// Defines all configuration options related to reply SURBs.
#[tsify(optional)]
pub reply_surbs: Option<ReplySurbsWasmOverride>,
}
impl From<DebugWasmOverride> for DebugWasm {
fn from(value: DebugWasmOverride) -> Self {
DebugWasm {
traffic: value.traffic.map(Into::into).unwrap_or_default(),
cover_traffic: value.cover_traffic.map(Into::into).unwrap_or_default(),
gateway_connection: value.gateway_connection.map(Into::into).unwrap_or_default(),
acknowledgements: value.acknowledgements.map(Into::into).unwrap_or_default(),
topology: value.topology.map(Into::into).unwrap_or_default(),
reply_surbs: value.reply_surbs.map(Into::into).unwrap_or_default(),
}
}
}
impl From<DebugWasmOverride> for ConfigDebug {
fn from(value: DebugWasmOverride) -> Self {
let debug_wasm: DebugWasm = value.into();
debug_wasm.into()
}
}
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct TrafficWasmOverride {
/// 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.
#[tsify(optional)]
pub average_packet_delay_ms: Option<u32>,
/// 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.
#[tsify(optional)]
pub message_sending_average_delay_ms: Option<u32>,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
#[tsify(optional)]
pub disable_main_poisson_packet_distribution: Option<bool>,
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
#[tsify(optional)]
pub use_extended_packet_size: Option<bool>,
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
#[tsify(optional)]
pub use_outfox: Option<bool>,
}
impl From<TrafficWasmOverride> for TrafficWasm {
fn from(value: TrafficWasmOverride) -> Self {
let def = TrafficWasm::default();
TrafficWasm {
average_packet_delay_ms: value
.average_packet_delay_ms
.unwrap_or(def.average_packet_delay_ms),
message_sending_average_delay_ms: value
.message_sending_average_delay_ms
.unwrap_or(def.message_sending_average_delay_ms),
disable_main_poisson_packet_distribution: value
.disable_main_poisson_packet_distribution
.unwrap_or(def.disable_main_poisson_packet_distribution),
use_extended_packet_size: value
.use_extended_packet_size
.unwrap_or(def.use_extended_packet_size),
use_outfox: value.use_outfox.unwrap_or(def.use_outfox),
}
}
}
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct CoverTrafficWasmOverride {
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
#[tsify(optional)]
pub loop_cover_traffic_average_delay_ms: Option<u32>,
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
/// Only applicable if `secondary_packet_size` is enabled.
#[tsify(optional)]
pub cover_traffic_primary_size_ratio: Option<f64>,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
#[tsify(optional)]
pub disable_loop_cover_traffic_stream: Option<bool>,
}
impl From<CoverTrafficWasmOverride> for CoverTrafficWasm {
fn from(value: CoverTrafficWasmOverride) -> Self {
let def = CoverTrafficWasm::default();
CoverTrafficWasm {
loop_cover_traffic_average_delay_ms: value
.loop_cover_traffic_average_delay_ms
.unwrap_or(def.loop_cover_traffic_average_delay_ms),
cover_traffic_primary_size_ratio: value
.cover_traffic_primary_size_ratio
.unwrap_or(def.cover_traffic_primary_size_ratio),
disable_loop_cover_traffic_stream: value
.disable_loop_cover_traffic_stream
.unwrap_or(def.disable_loop_cover_traffic_stream),
}
}
}
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct GatewayConnectionWasmOverride {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
#[tsify(optional)]
pub gateway_response_timeout_ms: Option<u32>,
}
impl From<GatewayConnectionWasmOverride> for GatewayConnectionWasm {
fn from(value: GatewayConnectionWasmOverride) -> Self {
let def = GatewayConnectionWasm::default();
GatewayConnectionWasm {
gateway_response_timeout_ms: value
.gateway_response_timeout_ms
.unwrap_or(def.gateway_response_timeout_ms),
}
}
}
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct AcknowledgementsWasmOverride {
/// 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.
#[tsify(optional)]
pub average_ack_delay_ms: Option<u32>,
/// 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.
#[tsify(optional)]
pub ack_wait_multiplier: Option<f64>,
/// 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.
#[tsify(optional)]
pub ack_wait_addition_ms: Option<u32>,
}
impl From<AcknowledgementsWasmOverride> for AcknowledgementsWasm {
fn from(value: AcknowledgementsWasmOverride) -> Self {
let def = AcknowledgementsWasm::default();
AcknowledgementsWasm {
average_ack_delay_ms: value
.average_ack_delay_ms
.unwrap_or(def.average_ack_delay_ms),
ack_wait_multiplier: value.ack_wait_multiplier.unwrap_or(def.ack_wait_multiplier),
ack_wait_addition_ms: value
.ack_wait_addition_ms
.unwrap_or(def.ack_wait_addition_ms),
}
}
}
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct TopologyWasmOverride {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
#[tsify(optional)]
pub topology_refresh_rate_ms: Option<u32>,
/// 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.
#[tsify(optional)]
pub topology_resolution_timeout_ms: Option<u32>,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
#[tsify(optional)]
pub disable_refreshing: Option<bool>,
}
impl From<TopologyWasmOverride> for TopologyWasm {
fn from(value: TopologyWasmOverride) -> Self {
let def = TopologyWasm::default();
TopologyWasm {
topology_refresh_rate_ms: value
.topology_refresh_rate_ms
.unwrap_or(def.topology_refresh_rate_ms),
topology_resolution_timeout_ms: value
.topology_resolution_timeout_ms
.unwrap_or(def.topology_resolution_timeout_ms),
disable_refreshing: value.disable_refreshing.unwrap_or(def.disable_refreshing),
}
}
}
#[derive(Tsify, Debug, Copy, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct ReplySurbsWasmOverride {
/// 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.
#[tsify(optional)]
pub minimum_reply_surb_storage_threshold: Option<usize>,
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
#[tsify(optional)]
pub maximum_reply_surb_storage_threshold: Option<usize>,
/// Defines the minimum number of reply surbs the client would request.
#[tsify(optional)]
pub minimum_reply_surb_request_size: Option<u32>,
/// Defines the maximum number of reply surbs the client would request.
#[tsify(optional)]
pub maximum_reply_surb_request_size: Option<u32>,
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
#[tsify(optional)]
pub maximum_allowed_reply_surb_request_size: Option<u32>,
/// 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.
#[tsify(optional)]
pub maximum_reply_surb_rerequest_waiting_period_ms: Option<u32>,
/// 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
#[tsify(optional)]
pub maximum_reply_surb_drop_waiting_period_ms: Option<u32>,
/// 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.
#[tsify(optional)]
pub maximum_reply_surb_age_ms: Option<u32>,
/// 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.
#[tsify(optional)]
pub maximum_reply_key_age_ms: Option<u32>,
}
impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
fn from(value: ReplySurbsWasmOverride) -> Self {
let def = ReplySurbsWasm::default();
ReplySurbsWasm {
minimum_reply_surb_storage_threshold: value
.minimum_reply_surb_storage_threshold
.unwrap_or(def.minimum_reply_surb_storage_threshold),
maximum_reply_surb_storage_threshold: value
.maximum_reply_surb_storage_threshold
.unwrap_or(def.maximum_reply_surb_storage_threshold),
minimum_reply_surb_request_size: value
.minimum_reply_surb_request_size
.unwrap_or(def.minimum_reply_surb_request_size),
maximum_reply_surb_request_size: value
.maximum_reply_surb_request_size
.unwrap_or(def.maximum_reply_surb_request_size),
maximum_allowed_reply_surb_request_size: value
.maximum_allowed_reply_surb_request_size
.unwrap_or(def.maximum_allowed_reply_surb_request_size),
maximum_reply_surb_rerequest_waiting_period_ms: value
.maximum_reply_surb_rerequest_waiting_period_ms
.unwrap_or(def.maximum_reply_surb_rerequest_waiting_period_ms),
maximum_reply_surb_drop_waiting_period_ms: value
.maximum_reply_surb_drop_waiting_period_ms
.unwrap_or(def.maximum_reply_surb_drop_waiting_period_ms),
maximum_reply_surb_age_ms: value
.maximum_reply_surb_age_ms
.unwrap_or(def.maximum_reply_surb_age_ms),
maximum_reply_key_age_ms: value
.maximum_reply_key_age_ms
.unwrap_or(def.maximum_reply_key_age_ms),
}
}
}
@@ -1,30 +1,22 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::storage::error::ClientStorageError;
use crate::storage::wasm_client_traits::WasmClientStorageError;
use crate::topology::WasmTopologyError;
use js_sys::Promise;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::error::ClientCoreError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_gateway_client::error::GatewayClientError;
use nym_node_tester_utils::error::NetworkTestingError;
use nym_sphinx::addressing::clients::RecipientFormattingError;
use nym_sphinx::anonymous_replies::requests::InvalidAnonymousSenderTagRepresentation;
use nym_topology::NymTopologyError;
use nym_validator_client::ValidatorClientError;
use thiserror::Error;
use wasm_bindgen::JsValue;
use wasm_utils::simple_js_error;
use wasm_utils::wasm_error;
// might as well start using well-defined error enum...
#[derive(Debug, Error)]
pub enum WasmClientError {
#[error(
"A node test is already in progress. Wait for it to finish before starting another one."
)]
TestInProgress,
pub enum WasmCoreError {
#[error("experienced an issue with internal client components: {source}")]
BaseClientError {
#[from]
@@ -58,12 +50,6 @@ pub enum WasmClientError {
source: NymTopologyError,
},
#[error("failed to test the node: {source}")]
NodeTestingFailure {
#[from]
source: NetworkTestingError,
},
#[error("{raw} is not a valid url: {source}")]
MalformedUrl {
raw: String,
@@ -92,9 +78,15 @@ pub enum WasmClientError {
},
#[error(transparent)]
StorageError {
BaseStorageError {
#[from]
source: ClientStorageError,
source: wasm_storage::error::StorageError,
},
#[error(transparent)]
ClientStorageError {
#[from]
source: WasmClientStorageError,
},
#[error("this client has already registered with a gateway: {gateway_config:?}")]
@@ -103,20 +95,4 @@ pub enum WasmClientError {
},
}
impl WasmClientError {
pub fn into_rejected_promise(self) -> Promise {
self.into()
}
}
impl From<WasmClientError> for JsValue {
fn from(value: WasmClientError) -> Self {
simple_js_error(value.to_string())
}
}
impl From<WasmClientError> for Promise {
fn from(value: WasmClientError) -> Self {
Promise::reject(&value.into())
}
}
wasm_error!(WasmCoreError);
@@ -1,9 +1,9 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmClientError;
use crate::error::WasmCoreError;
use crate::storage::wasm_client_traits::WasmClientStorage;
use crate::storage::ClientStorage;
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config;
@@ -11,51 +11,51 @@ use nym_client_core::init::helpers::current_gateways;
use nym_client_core::init::{setup_gateway_from, GatewaySetup, InitialisationResult};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::{gateway, NymTopology};
use nym_topology::{gateway, NymTopology, SerializableNymTopology};
use nym_validator_client::client::IdentityKey;
use nym_validator_client::NymApiClient;
use rand::thread_rng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::PromisableResult;
use wasm_utils::error::PromisableResult;
pub use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no disk_persistence
pub(crate) fn setup_reply_surb_storage_backend(
config: config::ReplySurbs,
) -> browser_backend::Backend {
pub fn setup_reply_surb_storage_backend(config: config::ReplySurbs) -> browser_backend::Backend {
browser_backend::Backend::new(
config.minimum_reply_surb_storage_threshold,
config.maximum_reply_surb_storage_threshold,
)
}
pub(crate) fn parse_recipient(recipient: &str) -> Result<Recipient, WasmClientError> {
pub fn parse_recipient(recipient: &str) -> Result<Recipient, WasmCoreError> {
Recipient::try_from_base58_string(recipient).map_err(|source| {
WasmClientError::MalformedRecipient {
WasmCoreError::MalformedRecipient {
raw: recipient.to_string(),
source,
}
})
}
pub(crate) fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, WasmClientError> {
pub fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, WasmCoreError> {
AnonymousSenderTag::try_from_base58_string(tag).map_err(|source| {
WasmClientError::MalformedSenderTag {
WasmCoreError::MalformedSenderTag {
raw: tag.to_string(),
source,
}
})
}
pub(crate) async fn current_network_topology_async(
pub async fn current_network_topology_async(
nym_api_url: String,
) -> Result<WasmNymTopology, WasmClientError> {
) -> Result<SerializableNymTopology, WasmCoreError> {
let url: Url = match nym_api_url.parse() {
Ok(url) => url,
Err(source) => {
return Err(WasmClientError::MalformedUrl {
return Err(WasmCoreError::MalformedUrl {
raw: nym_api_url,
source,
})
@@ -69,11 +69,13 @@ pub(crate) async fn current_network_topology_async(
Ok(NymTopology::from_detailed(mixnodes, gateways).into())
}
#[wasm_bindgen]
#[wasm_bindgen(js_name = "currentNetworkTopology")]
pub fn current_network_topology(nym_api_url: String) -> Promise {
// blame js for that serde conversion
future_to_promise(async move {
current_network_topology_async(nym_api_url)
.await
.map(|topology| serde_wasm_bindgen::to_value(&topology).unwrap())
.into_promise_result()
})
}
@@ -82,7 +84,7 @@ async fn setup_gateway(
client_store: &ClientStorage,
chosen_gateway: Option<IdentityKey>,
gateways: &[gateway::Node],
) -> Result<InitialisationResult, WasmClientError> {
) -> Result<InitialisationResult, WasmCoreError> {
let setup = if client_store.has_full_gateway_info().await? {
GatewaySetup::MustLoad
} else {
@@ -94,21 +96,21 @@ async fn setup_gateway(
.map_err(Into::into)
}
pub(crate) async fn setup_gateway_from_api(
pub async fn setup_gateway_from_api(
client_store: &ClientStorage,
chosen_gateway: Option<IdentityKey>,
nym_apis: &[Url],
) -> Result<InitialisationResult, WasmClientError> {
) -> Result<InitialisationResult, WasmCoreError> {
let mut rng = thread_rng();
let gateways = current_gateways(&mut rng, nym_apis).await?;
setup_gateway(client_store, chosen_gateway, &gateways).await
}
pub(crate) async fn setup_from_topology(
pub async fn setup_from_topology(
explicit_gateway: Option<IdentityKey>,
topology: &NymTopology,
client_store: &ClientStorage,
) -> Result<InitialisationResult, WasmClientError> {
) -> Result<InitialisationResult, WasmCoreError> {
let gateways = topology.gateways();
setup_gateway(client_store, explicit_gateway, gateways).await
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(target_arch = "wasm32")]
pub mod config;
#[cfg(target_arch = "wasm32")]
pub mod error;
#[cfg(target_arch = "wasm32")]
pub mod helpers;
#[cfg(target_arch = "wasm32")]
pub mod storage;
#[cfg(target_arch = "wasm32")]
pub mod topology;
// re-export types for ease of use
pub use nym_bandwidth_controller::BandwidthController;
pub use nym_client_core::*;
pub use nym_client_core::{
client::key_manager::ManagedKeys,
error::ClientCoreError,
init::{InitialisationDetails, InitialisationResult},
};
pub use nym_gateway_client::{error::GatewayClientError, GatewayClient};
pub use nym_sphinx::{
addressing::{clients::Recipient, nodes::NodeIdentity},
params::PacketType,
receiver::ReconstructedMessage,
};
pub use nym_task;
pub use nym_topology::{HardcodedTopologyProvider, MixLayer, NymTopology, TopologyProvider};
pub use nym_validator_client::nym_api::Client as ApiClient;
pub use nym_validator_client::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient};
// TODO: that's a very nasty import path. it should come from contracts instead!
pub use nym_validator_client::client::IdentityKey;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[cfg(target_arch = "wasm32")]
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
@@ -1,7 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::storage::error::ClientStorageError;
use crate::config::BaseClientConfig;
use crate::error::WasmCoreError;
use crate::helpers::setup_reply_surb_storage_backend;
use crate::storage::wasm_client_traits::WasmClientStorage;
use crate::storage::ClientStorage;
use async_trait::async_trait;
use nym_client_core::client::base_client::storage::gateway_details::{
@@ -22,6 +25,17 @@ pub struct FullWasmClientStorage {
pub(crate) credential_storage: EphemeralCredentialStorage,
}
impl FullWasmClientStorage {
// TODO: I dont like that base_config type, it should be something wasm-specific.
pub fn new(base_config: &BaseClientConfig, base_storage: ClientStorage) -> Self {
FullWasmClientStorage {
keys_and_gateway_store: base_storage,
reply_storage: setup_reply_surb_storage_backend(base_config.debug.reply_surbs),
credential_storage: EphemeralCredentialStorage::default(),
}
}
}
impl MixnetClientStorage for FullWasmClientStorage {
type KeyStore = ClientStorage;
type ReplyStore = browser_backend::Backend;
@@ -52,7 +66,7 @@ impl MixnetClientStorage for FullWasmClientStorage {
#[async_trait(?Send)]
impl KeyStore for ClientStorage {
type StorageError = ClientStorageError;
type StorageError = WasmCoreError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
console_log!("attempting to load cryptographic keys...");
@@ -86,7 +100,7 @@ impl KeyStore for ClientStorage {
#[async_trait(?Send)]
impl GatewayDetailsStore for ClientStorage {
type StorageError = ClientStorageError;
type StorageError = WasmCoreError;
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails, Self::StorageError> {
self.must_read_gateway_details().await
@@ -96,6 +110,6 @@ impl GatewayDetailsStore for ClientStorage {
&self,
details: &PersistedGatewayDetails,
) -> Result<(), Self::StorageError> {
self.store_gateway_details(details).await
<Self as WasmClientStorage>::store_gateway_details(self, details).await
}
}
+151
View File
@@ -0,0 +1,151 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmCoreError;
use crate::storage::wasm_client_traits::{v1, WasmClientStorage};
use async_trait::async_trait;
use js_sys::{Array, Promise};
use serde::de::DeserializeOwned;
use serde::Serialize;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_storage::traits::BaseWasmStorage;
use wasm_storage::{IdbVersionChangeEvent, WasmStorage};
use wasm_utils::error::PromisableResult;
use zeroize::Zeroizing;
pub mod core_client_traits;
pub mod wasm_client_traits;
const STORAGE_NAME_PREFIX: &str = "wasm-client-storage";
const STORAGE_VERSION: u32 = 1;
#[wasm_bindgen]
pub struct ClientStorage {
#[allow(dead_code)]
pub(crate) name: String,
pub(crate) inner: WasmStorage,
}
#[wasm_bindgen]
impl ClientStorage {
fn db_name(client_id: &str) -> String {
format!("{STORAGE_NAME_PREFIX}-{client_id}")
}
pub async fn new_async(
client_id: &str,
passphrase: Option<String>,
) -> Result<ClientStorage, WasmCoreError> {
let name = Self::db_name(client_id);
// make sure the password is zeroized when no longer used, especially if we error out.
// special care must be taken on JS side to ensure it's correctly used there.
let passphrase = Zeroizing::new(passphrase);
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
// works with an unsigned integer.
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
let old_version = evt.old_version() as u32;
if old_version < 1 {
// migrating to version 1
let db = evt.db();
db.create_object_store(v1::KEYS_STORE)?;
db.create_object_store(v1::CORE_STORE)?;
}
Ok(())
});
let inner = WasmStorage::new(
&name,
STORAGE_VERSION,
migrate_fn,
passphrase.as_ref().map(|p| p.as_bytes()),
)
.await?;
Ok(ClientStorage { inner, name })
}
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(client_id: String, passphrase: String) -> Promise {
future_to_promise(async move {
Self::new_async(&client_id, Some(passphrase))
.await
.into_promise_result()
})
}
pub fn new_unencrypted(client_id: String) -> Promise {
future_to_promise(async move {
Self::new_async(&client_id, None)
.await
.into_promise_result()
})
}
}
#[async_trait(?Send)]
impl BaseWasmStorage for ClientStorage {
type StorageError = WasmCoreError;
async fn exists(db_name: &str) -> Result<bool, Self::StorageError> {
Ok(WasmStorage::exists(db_name).await?)
}
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
where
T: DeserializeOwned,
K: JsCast,
{
Ok(self.inner.read_value(store, key).await?)
}
async fn store_value<T, K>(
&self,
store: &str,
key: K,
value: &T,
) -> Result<(), Self::StorageError>
where
T: Serialize,
K: JsCast,
{
Ok(self.inner.store_value(store, key, value).await?)
}
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
where
K: JsCast,
{
Ok(self.inner.remove_value(store, key).await?)
}
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
where
K: JsCast,
{
Ok(self.inner.has_value(store, key).await?)
}
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
where
K: JsCast,
{
Ok(self.inner.key_count(store, key).await?)
}
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
Ok(self.inner.get_all_keys(store).await?)
}
}
#[async_trait(?Send)]
impl WasmClientStorage for ClientStorage {
type StorageError = WasmCoreError;
}
@@ -0,0 +1,220 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_client_core::client::base_client::storage::gateway_details::PersistedGatewayDetails;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_client::SharedKeys;
use nym_sphinx_acknowledgements::AckKey;
use std::error::Error;
use thiserror::Error;
use wasm_bindgen::JsValue;
use wasm_storage::traits::BaseWasmStorage;
// v1 tables
pub(crate) mod v1 {
// stores
pub const KEYS_STORE: &str = "keys";
pub const CORE_STORE: &str = "core";
// keys
// pub const CONFIG: &str = "config";
pub const GATEWAY_DETAILS: &str = "gateway_details";
pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair";
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair";
// TODO: for those we could actually use the subtle crypto storage
pub const AES128CTR_ACK_KEY: &str = "aes128ctr_ack_key";
pub const AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS: &str = "aes128ctr_blake3_hmac_gateway_keys";
}
#[derive(Debug, Error)]
pub enum WasmClientStorageError {
#[error("{typ} cryptographic key is not available in storage")]
CryptoKeyNotInStorage { typ: String },
#[error("the prior gateway details are not available in the storage")]
GatewayDetailsNotInStorage,
}
#[async_trait(?Send)]
pub trait WasmClientStorage: BaseWasmStorage {
type StorageError: Error
+ From<<Self as BaseWasmStorage>::StorageError>
+ From<WasmClientStorageError>;
async fn may_read_gateway_details(
&self,
) -> Result<Option<PersistedGatewayDetails>, <Self as WasmClientStorage>::StorageError> {
self.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_DETAILS))
.await
.map_err(Into::into)
}
async fn must_read_gateway_details(
&self,
) -> Result<PersistedGatewayDetails, <Self as WasmClientStorage>::StorageError> {
self.may_read_gateway_details()
.await?
.ok_or(WasmClientStorageError::GatewayDetailsNotInStorage)
.map_err(Into::into)
}
async fn may_read_identity_keypair(
&self,
) -> Result<Option<identity::KeyPair>, <Self as WasmClientStorage>::StorageError> {
self.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
)
.await
.map_err(Into::into)
}
async fn may_read_encryption_keypair(
&self,
) -> Result<Option<encryption::KeyPair>, <Self as WasmClientStorage>::StorageError> {
self.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
)
.await
.map_err(Into::into)
}
async fn may_read_ack_key(
&self,
) -> Result<Option<AckKey>, <Self as WasmClientStorage>::StorageError> {
self.read_value(v1::KEYS_STORE, JsValue::from_str(v1::AES128CTR_ACK_KEY))
.await
.map_err(Into::into)
}
async fn may_read_gateway_shared_key(
&self,
) -> Result<Option<SharedKeys>, <Self as WasmClientStorage>::StorageError> {
self.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
)
.await
.map_err(Into::into)
}
async fn must_read_identity_keypair(
&self,
) -> Result<identity::KeyPair, <Self as WasmClientStorage>::StorageError> {
self.may_read_identity_keypair()
.await?
.ok_or(WasmClientStorageError::CryptoKeyNotInStorage {
typ: v1::ED25519_IDENTITY_KEYPAIR.to_string(),
})
.map_err(Into::into)
}
async fn must_read_encryption_keypair(
&self,
) -> Result<encryption::KeyPair, <Self as WasmClientStorage>::StorageError> {
self.may_read_encryption_keypair()
.await?
.ok_or(WasmClientStorageError::CryptoKeyNotInStorage {
typ: v1::X25519_ENCRYPTION_KEYPAIR.to_string(),
})
.map_err(Into::into)
}
async fn must_read_ack_key(&self) -> Result<AckKey, <Self as WasmClientStorage>::StorageError> {
self.may_read_ack_key()
.await?
.ok_or(WasmClientStorageError::CryptoKeyNotInStorage {
typ: v1::AES128CTR_ACK_KEY.to_string(),
})
.map_err(Into::into)
}
async fn must_read_gateway_shared_key(
&self,
) -> Result<SharedKeys, <Self as WasmClientStorage>::StorageError> {
self.may_read_gateway_shared_key()
.await?
.ok_or(WasmClientStorageError::CryptoKeyNotInStorage {
typ: v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS.to_string(),
})
.map_err(Into::into)
}
async fn store_identity_keypair(
&self,
keypair: &identity::KeyPair,
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
self.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
keypair,
)
.await
.map_err(Into::into)
}
async fn store_encryption_keypair(
&self,
keypair: &encryption::KeyPair,
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
self.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
keypair,
)
.await
.map_err(Into::into)
}
async fn store_ack_key(
&self,
key: &AckKey,
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
self.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_ACK_KEY),
key,
)
.await
.map_err(Into::into)
}
async fn store_gateway_shared_key(
&self,
key: &SharedKeys,
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
self.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
key,
)
.await
.map_err(Into::into)
}
async fn store_gateway_details(
&self,
gateway_endpoint: &PersistedGatewayDetails,
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
self.store_value(
v1::CORE_STORE,
JsValue::from_str(v1::GATEWAY_DETAILS),
gateway_endpoint,
)
.await
.map_err(Into::into)
}
async fn has_full_gateway_info(
&self,
) -> Result<bool, <Self as WasmClientStorage>::StorageError> {
let has_keys = self.may_read_gateway_shared_key().await?.is_some();
let has_details = self.may_read_gateway_details().await?.is_some();
Ok(has_keys && has_details)
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::GatewayEndpointConfig;
pub use nym_topology::SerializableNymTopology;
use nym_topology::SerializableTopologyError;
use nym_validator_client::client::IdentityKeyRef;
use wasm_utils::console_log;
// redeclare this as a type alias for easy of use
pub type WasmTopologyError = SerializableTopologyError;
// helper trait to define extra functionality on the external type that we used to have here before
pub trait SerializableTopologyExt {
fn print(&self);
fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
}
fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool;
}
impl SerializableTopologyExt for SerializableNymTopology {
fn print(&self) {
if !self.mixnodes.is_empty() {
console_log!("mixnodes:");
for (layer, nodes) in &self.mixnodes {
console_log!("\tlayer {layer}:");
for node in nodes {
// console_log!("\t\t{} - {}", node.mix_id, node.identity_key)
console_log!("\t\t{} - {:?}", node.mix_id, node)
}
}
} else {
console_log!("NO MIXNODES")
}
if !self.gateways.is_empty() {
console_log!("gateways:");
for gateway in &self.gateways {
// console_log!("\t{}", gateway.identity_key)
console_log!("\t{:?}", gateway)
}
} else {
console_log!("NO GATEWAYS")
}
}
fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
self.gateways.iter().any(|g| g.identity_key == gateway_id)
}
}
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "wasm-storage"
version = "0.1.0"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { workspace = true }
futures = { workspace = true }
js-sys = { workspace = true }
wasm-bindgen = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde-wasm-bindgen = { workspace = true }
indexed_db_futures = { version = " 0.3.0"}
thiserror = { workspace = true }
nym-store-cipher = { path = "../../store-cipher", features = ["json"] }
wasm-utils = { path = "../utils", default-features = false }
@@ -1,11 +1,11 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::simple_js_error;
use indexed_db_futures::web_sys::DomException;
use serde_wasm_bindgen::Error;
use thiserror::Error;
use wasm_bindgen::JsValue;
use wasm_utils::error::simple_js_error;
#[derive(Debug, Error)]
pub enum StorageError {
@@ -1,22 +1,23 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::console_log;
use crate::storage::cipher_export::StoredExportedStoreCipher;
use crate::storage::error::StorageError;
use futures::TryFutureExt;
use crate::cipher_export::StoredExportedStoreCipher;
use crate::error::StorageError;
use nym_store_cipher::{
Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned,
Version,
};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::future::IntoFuture;
use wasm_bindgen::JsValue;
use wasm_utils::console_log;
pub use indexed_db_futures::prelude::*;
mod cipher_export;
pub mod error;
pub mod traits;
pub const CIPHER_INFO_STORE: &str = "_cipher_store";
pub const CIPHER_STORE_EXPORT: &str = "cipher_store_export_info";
@@ -87,6 +88,16 @@ impl WasmStorage {
})
}
pub async fn delete(self) -> Result<(), StorageError> {
self.inner.0.delete()?.into_future().await?;
Ok(())
}
pub async fn remove(db_name: &str) -> Result<(), StorageError> {
IdbDatabase::delete_by_name(db_name)?.into_future().await?;
Ok(())
}
pub async fn exists(db_name: &str) -> Result<bool, StorageError> {
let db_req: OpenDbRequest = IdbDatabase::open(db_name)?;
let db: IdbDatabase = db_req.into_future().await?;
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::WasmStorage;
use async_trait::async_trait;
use js_sys::Array;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::error::Error;
#[async_trait(?Send)]
pub trait BaseWasmStorage {
type StorageError: Error;
async fn exists(db_name: &str) -> Result<bool, Self::StorageError>;
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
where
T: DeserializeOwned,
K: wasm_bindgen::JsCast;
async fn store_value<T, K>(
&self,
store: &str,
key: K,
value: &T,
) -> Result<(), Self::StorageError>
where
T: Serialize,
K: wasm_bindgen::JsCast;
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
where
K: wasm_bindgen::JsCast;
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
where
K: wasm_bindgen::JsCast;
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
where
K: wasm_bindgen::JsCast;
async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, Self::StorageError>;
}
#[async_trait(?Send)]
impl BaseWasmStorage for WasmStorage {
type StorageError = crate::error::StorageError;
async fn exists(db_name: &str) -> Result<bool, Self::StorageError> {
WasmStorage::exists(db_name).await
}
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
where
T: DeserializeOwned,
K: wasm_bindgen::JsCast,
{
self.read_value(store, key).await
}
async fn store_value<T, K>(
&self,
store: &str,
key: K,
value: &T,
) -> Result<(), Self::StorageError>
where
T: Serialize,
K: wasm_bindgen::JsCast,
{
self.store_value(store, key, value).await
}
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
where
K: wasm_bindgen::JsCast,
{
self.remove_value(store, key).await
}
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
where
K: wasm_bindgen::JsCast,
{
self.has_value(store, key).await
}
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
where
K: wasm_bindgen::JsCast,
{
self.key_count(store, key).await
}
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
self.get_all_keys(store).await
}
}

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