Compare commits

..

2 Commits

Author SHA1 Message Date
Tommy 8764ddbb6c wallet-name changed 2021-10-26 16:09:50 +03:00
Tommy 7338640f36 remove 2021-10-26 15:06:05 +03:00
967 changed files with 57829 additions and 165415 deletions
+3 -3
View File
@@ -15,8 +15,8 @@
* @futurechimp @mmsinclair * @futurechimp @mmsinclair
# Rust rules: # Rust rules:
*.rs @durch @futurechimp @jstuczyn @neacsu @octol *.rs @durch @futurechimp @jstuczyn @neacsu
Cargo.* @durch @futurechimp @jstuczyn @neacsu @octol Cargo.* @durch @futurechimp @jstuczyn @neacsu
# JS rules: # JS rules:
*.js @mmsinclair @fmtabbara @Aid19801 *.js @mmsinclair @fmtabbara @Aid19801
@@ -36,5 +36,5 @@ Cargo.* @durch @futurechimp @jstuczyn @neacsu @octol
# Explorer and wallet should probably get looked by the product team # Explorer and wallet should probably get looked by the product team
/explorer/ @nymtech/product /explorer/ @nymtech/product
/nym-wallet/ @nymtech/product /tauri-wallet/ @nymtech/product
/wallet-web/ @nymtech/product /wallet-web/ @nymtech/product
-32
View File
@@ -1,32 +0,0 @@
---
name: Feature request
about: Suggest an enhancement to the product
title: "[Feature Request]"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is...
**Is your request a feature not related to an existing problem? A new feature.**
For example.
- Given I am using the nym wallet
- When I transfer nym tokens across the network
- Then I want to have an url link in the wallet which navigates outside the application to the nym-explorer
**Where does the feature fit in the Nym real estate?**
- Application / UI
**What is this solving?**
How will this improve the product...
**Is this an update to packages or libraries?**
If so, please list them. If not, please ignore this section.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-43
View File
@@ -1,43 +0,0 @@
---
name: Report
about: To help identify and reproduce issues
title: "[Issue]"
labels: bug, bug-needs-triage, qa
assignees: tommyv1987
---
**Describe the issue**
A clear and concise description of what the issue is...
**Expected behaviour**
A clear and concise description of what you expected to happen...
**Stack Traces**
If there are stack traces or logs, please provide them here...
**Steps to Reproduce**
Steps to reproduce the behaviour, if you're familiar with BDD syntax, please write it in this style:
- Given I was doing X
- And I installed Y
- When I actioned Y
- Then I expect Z
*An example:*
- Given I was setting up a mix-node following the instructions in the docs
- And I successfully bonded my node via the the wallet
- When I went to start my mixnode
- Then I was presented with an error
**Screenshots**
If applicable, add screenshots to help explain your problem...
**Which area of Nym were you using?**
- UI: [e.g. Websites - network-explorer, nym-website]
- Application: [e.g Gateway, Client, Wallet]
- OS: [e.g. Ubuntu 20.x, MacOs Big Sur, Windows 10]
- Browser: [e.g Chrome (if applicable)]
- Version: [e.g. nym binary(0.11.0), browser(94.0)]
**Additional context**
Please provide any other information
+83 -25
View File
@@ -1,22 +1,20 @@
name: Continuous integration name: Continuous integration
on: on: [push, pull_request]
push:
paths-ignore:
- 'explorer/**'
pull_request:
paths-ignore:
- 'explorer/**'
jobs: jobs:
build: build:
runs-on: [ self-hosted, custom-linux-exoscale ] runs-on: ${{ matrix.os }}
# Enable sccache via environment variable continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.os == 'windows-latest' }}
env: strategy:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache matrix:
rust: [stable, beta, nightly]
os: [ubuntu-latest, macos-latest, windows-latest]
steps: steps:
- name: Install Dependencies (Linux) - name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v2 uses: actions/checkout@v2
@@ -25,7 +23,7 @@ jobs:
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: stable toolchain: ${{ matrix.rust }}
override: true override: true
components: rustfmt, clippy components: rustfmt, clippy
@@ -47,32 +45,92 @@ jobs:
command: fmt command: fmt
args: --all -- --check args: --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy - name: Run clippy
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with: with:
command: clippy command: clippy
args: -- -D warnings args: -- -D warnings
- name: Build all binaries with coconut enabled # COCONUT stuff
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
# BUILD
- name: Build gateway with coconut feature
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: build command: build
args: --all --features=coconut args: --bin nym-gateway --features=coconut
- name: Run all tests with coconut enabled - name: Build native client with coconut feature
uses: actions-rs/cargo@v1
with:
command: build
args: --bin nym-client --features=coconut
- name: Build socks5 client with coconut feature
uses: actions-rs/cargo@v1
with:
command: build
args: --bin nym-socks5-client --features=coconut
- name: Build validator-api with coconut feature
uses: actions-rs/cargo@v1
with:
command: build
args: --bin nym-validator-api --features=coconut
# TEST
- name: Test gateway with coconut feature
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: --all --features=coconut args: --bin nym-gateway --features=coconut
- name: Run clippy with coconut enabled - name: Test native client with coconut feature
uses: actions-rs/cargo@v1
with:
command: test
args: --bin nym-client --features=coconut
- name: Test socks5 client with coconut feature
uses: actions-rs/cargo@v1
with:
command: test
args: --bin nym-socks5-client --features=coconut
- name: Test validator-api with coconut feature
uses: actions-rs/cargo@v1
with:
command: test
args: --bin nym-validator-api --features=coconut
# CLIPPY
- name: Run clippy on gateway with coconut feature
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: clippy command: clippy
args: --features=coconut -- -D warnings args: --bin nym-gateway --features=coconut -- -D warnings
- name: Run clippy on native client with coconut feature
uses: actions-rs/cargo@v1
with:
command: clippy
args: --bin nym-client --features=coconut -- -D warnings
- name: Run clippy on socks5 client with coconut feature
uses: actions-rs/cargo@v1
with:
command: clippy
args: --bin nym-socks5-client --features=coconut -- -D warnings
- name: Run clippy on validator-api with coconut feature
uses: actions-rs/cargo@v1
with:
command: clippy
args: --bin nym-validator-api --features=coconut -- -D warnings
@@ -1,19 +0,0 @@
[
{
"os":"ubuntu-latest",
"rust":"stable",
"runOnEvent":"always"
},
{
"os":"windows-latest",
"rust":"stable",
"runOnEvent":"pull_request"
},
{
"os":"macos-latest",
"rust":"stable",
"runOnEvent":"pull_request"
}
]
+14
View File
@@ -0,0 +1,14 @@
name: Clippy check
on: push
jobs:
clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: rustup component add clippy
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
@@ -1,14 +0,0 @@
[
{
"rust":"stable",
"runOnEvent":"always"
},
{
"rust":"beta",
"runOnEvent":"pull_request"
},
{
"rust":"nightly",
"runOnEvent":"pull_request"
}
]
-64
View File
@@ -1,64 +0,0 @@
name: Contracts
on:
push:
paths-ignore:
- 'explorer/**'
pull_request:
paths-ignore:
- 'explorer/**'
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
contracts:
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.rust == 'nightly' }}
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
env:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/Cargo.toml --all --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/Cargo.toml --all -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
+44
View File
@@ -0,0 +1,44 @@
name: Mixnet Contract
on: [push, pull_request]
jobs:
mixnet-contract:
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.rust == 'nightly' }}
strategy:
matrix:
rust: [ stable, beta, nightly ]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path contracts/mixnet/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/mixnet/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/mixnet/Cargo.toml -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/mixnet/Cargo.toml -- -D warnings
@@ -1,23 +0,0 @@
name: Linting for Network Explorer (eslint/prettier)
on:
pull_request:
paths:
- 'explorer/**'
defaults:
run:
working-directory: explorer
jobs:
build:
runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install
- name: Run ESLint
# GitHub should automatically annotate the PR
run: npm run lint
+4 -8
View File
@@ -11,7 +11,7 @@ defaults:
jobs: jobs:
build: build:
runs-on: custom-runner-linux runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install rsync - name: Install rsync
@@ -22,8 +22,6 @@ jobs:
node-version: '14' node-version: '14'
- run: npm install - run: npm install
continue-on-error: true continue-on-error: true
- name: Set environment from the example
run: cp .env.prod .env
- run: npm run test - run: npm run test
continue-on-error: true continue-on-error: true
- run: npm run build - run: npm run build
@@ -37,17 +35,15 @@ jobs:
SOURCE: "explorer/dist/" SOURCE: "explorer/dist/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }} REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/network-explorer-${{ env.GITHUB_REF_SLUG }} TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/" EXCLUDE: "/dist/, /node_modules/"
- name: Keybase - Node Install - name: Keybase - Node Install
run: npm install run: npm install
working-directory: .github/workflows/support-files working-directory: .github/workflows/support-files/messages
- name: Keybase - Send Notification - name: Keybase - Send Notification
env: env:
NYM_NOTIFICATION_KIND: network-explorer
NYM_PROJECT_NAME: "Network Explorer" NYM_PROJECT_NAME: "Network Explorer"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}" NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "network-explorer-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}" GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
@@ -57,4 +53,4 @@ jobs:
IS_SUCCESS: "${{ job.status == 'success' }}" IS_SUCCESS: "${{ job.status == 'success' }}"
uses: docker://keybaseio/client:stable-node uses: docker://keybaseio/client:stable-node
with: with:
args: .github/workflows/support-files/notifications/entry_point.sh args: .github/workflows/support-files/messages/entry_point_notifications.sh
-124
View File
@@ -1,124 +0,0 @@
name: Nightly builds
on:
schedule:
- cron: '14 4 * * *'
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v2
- 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`]'
build:
needs: 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 squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --all
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: -- -D warnings
# COCONUT stuff
- 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 binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --all --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --all --features=coconut
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --features=coconut -- -D warnings
notification:
needs: build
runs-on: ubuntu-latest
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v2
- name: Keybase - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
@@ -1,50 +0,0 @@
[
{
"os":"ubuntu-latest",
"rust":"stable",
"runOnEvent":"schedule"
},
{
"os":"windows-latest",
"rust":"stable",
"runOnEvent":"schedule"
},
{
"os":"macos-latest",
"rust":"stable",
"runOnEvent":"schedule"
},
{
"os":"ubuntu-latest",
"rust":"beta",
"runOnEvent":"schedule"
},
{
"os":"windows-latest",
"rust":"beta",
"runOnEvent":"schedule"
},
{
"os":"macos-latest",
"rust":"beta",
"runOnEvent":"schedule"
},
{
"os":"ubuntu-latest",
"rust":"nightly",
"runOnEvent":"schedule"
},
{
"os":"windows-latest",
"rust":"nightly",
"runOnEvent":"schedule"
},
{
"os":"macos-latest",
"rust":"nightly",
"runOnEvent":"schedule"
}
]
@@ -1,77 +0,0 @@
name: Publish Nym Wallet (MacOS)
on:
release:
types: [created]
defaults:
run:
working-directory: nym-wallet
jobs:
publish-tauri:
strategy:
fail-fast: false
matrix:
platform: [macos-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- name: Check the release tag starts with `nym-wallet-`
if: startsWith(github.ref, 'refs/tags/nym-wallet-') == false
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-wallet-...')
- name: Node v16
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install the Apple developer certificate for code signing
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$APPLE_CERTIFICATE" | base64 --decode --output $CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
- name: Install app dependencies and build it
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_IDENTITY_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
run: yarn && yarn build
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
with:
files: nym-wallet/target/release/bundle/dmg/*.dmg
- name: Clean up keychain
if: ${{ always() }}
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
@@ -1,46 +0,0 @@
name: Publish Nym Wallet (Ubuntu)
on:
release:
types: [created]
defaults:
run:
working-directory: nym-wallet
jobs:
publish-tauri:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- name: Tauri dependencies
run: >
sudo apt-get update &&
sudo apt-get install -y webkit2gtk-4.0
- name: Check the release tag starts with `nym-wallet-`
if: startsWith(github.ref, 'refs/tags/nym-wallet-') == false
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-wallet-...')
- name: Node v16
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install app dependencies and build it
run: yarn && yarn build
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
with:
files: nym-wallet/target/release/bundle/appimage/*.AppImage
-32
View File
@@ -1,32 +0,0 @@
name: Generate TS types
on:
push:
paths-ignore:
- "explorer/**"
pull_request:
paths-ignore:
- "explorer/**"
jobs:
nym-wallet-types:
runs-on: [ self-hosted, custom-linux-exoscale ]
# Enable sccache
env:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
if: ${{ github.event_name != 'pull_request' }}
steps:
- name: Prepare
run: sudo apt-get update && sudo apt-get install -y libpango1.0-dev libatk1.0-dev libgdk-pixbuf2.0-dev libsoup2.4-dev librust-gdk-dev libwebkit2gtk-4.0-dev
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Generate TS
run: cd nym-wallet/src-tauri && cargo test
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
with:
add: '["nym-wallet"]'
message: "[ci skip] Generate TS types"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+16 -21
View File
@@ -1,18 +1,18 @@
name: Webdriverio tests for nym wallet name: Webdriverio tests for nym wallet
on: on:
push: push:
paths: paths:
- "nym-wallet/**" - 'tauri-wallet/**'
defaults: defaults:
run: run:
working-directory: nym-wallet working-directory: tauri-wallet
jobs: jobs:
test: test:
name: wallet tests name: wallet tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -30,8 +30,8 @@ jobs:
- name: Install minimal stable - name: Install minimal stable
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: stable toolchain: stable
- name: Node v16 - name: Node v16
uses: actions/setup-node@v1 uses: actions/setup-node@v1
@@ -39,32 +39,27 @@ jobs:
node-version: 16.x node-version: 16.x
- name: Install yarn for building application - name: Install yarn for building application
run: yarn install run: yarn install
- name: Build application - name: Build application
run: yarn run webpack:build & yarn run tauri:build run: yarn run webpack:build & yarn run tauri:build
- name: Check binary exists
run: |
cd target/release/
(test -f nym-wallet && echo nym binary exists) || echo wallet does not exist
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
working-directory: nym-wallet/webdriver working-directory: tauri-wallet/webdriver
- name: Remove existing user datafile - name: Remove existing user datafile
uses: JesseTG/rm@v1.0.2 uses: JesseTG/rm@v1.0.2
with: with:
path: nym-wallet/webdriver/common/data/user-data.json path: tauri-wallet/webdriver/common/data/user-data.json
- name: Create user data json file - name: Create user data json file
id: create-json id: create-json
uses: jsdaniell/create-json@1.1.2 uses: jsdaniell/create-json@1.1.2
with: with:
name: "user-data.json" name: "user-data.json"
json: ${{ secrets.WALLET_USERDATA }} json: ${{ secrets.WALLET_USERDATA }}
dir: "nym-wallet/webdriver/common/data/" dir: 'tauri-wallet/webdriver/common/data/'
- name: Install tauri-driver - name: Install tauri-driver
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
@@ -73,5 +68,5 @@ jobs:
args: tauri-driver args: tauri-driver
- name: Launch tests - name: Launch tests
run: xvfb-run yarn test:runall run: xvfb-run yarn test:newuser
working-directory: nym-wallet/webdriver working-directory: tauri-wallet/webdriver
@@ -1,35 +0,0 @@
KEYBASE_NYM_CHANNEL=
KEYBASE_NYMBOT_USERNAME=
KEYBASE_NYMBOT_PAPERKEY=
NYM_NOTIFICATION_KIND=nightly
NYM_PROJECT_NAME=Nightly Build
#----------------------------------------------------------------
# Custom GitHub Actions mock env vars
IS_SUCCESS=true
#----------------------------------------------------------------
# GitHub Actions context mock env vars
GITHUB_SHA=abcdef
GITHUB_RUN_ID=123456
GITHUB_REPOSITORY=nymtech/nym
GITHUB_SERVER_URL=https://github.com
GIT_BRANCH_NAME=feature/testing-support-files
GIT_BRANCH=feature/testing-support-files
GIT_COMMIT_MESSAGE=This is the commit message
GITHUB_ACTOR=octocat
# add a Personal Access Token (PAT) generated from GitHub here for use in testing
GITHUB_TOKEN=
#----------------------------------------------------------------
# Network Explorer
NYM_CI_WWW_LOCATION=some-branch
NYM_CI_WWW_BASE=example.com
#----------------------------------------------------------------
# Nightly builds
WORKFLOW_CONCLUSION=success
SHOW_DEBUG=true
@@ -1,5 +0,0 @@
node_modules
.idea
# don't commit the lock file to avoid cross-platform issues
package-lock.json
-1
View File
@@ -1 +0,0 @@
16
-58
View File
@@ -1,58 +0,0 @@
# GitHub Actions Support Files
This is a collection of scripts and files to support GitHub Actions.
## Sending Notifications
These scripts send CI notifications to Keybase by creating messages from templates and env vars passed from GitHub Actions.
### Adding notifications to a GitHub Action
```
jobs:
build:
...
- name: Notifications - Node Install
run: npm install
working-directory: .github/workflows/support-files/notifications
- name: Notifications - Send
env:
NYM_NOTIFICATION_KIND: "my-component"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-network-explorer"
IS_SUCCESS: "${{ job.status == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
```
Notifications are run by adding the snippet above to a GitHub Action, and:
1. Installing node packages needed at run time
2. Set the env vars as required:
- `NYM_NOTIFICATION_KIND` matches the directory in `.github/workflows/support-files/${NYM_NOTIFICATION_KIND}` to provide the templates and extra scripting in `index.js`
- Keybase credentials, channel and other env vars for the status of the build and repo
3. Replacing the default entry point shell script on the `keybaseio/client:stable-node` docker image to run `.github/workflows/support-files/notifications/entry_point.sh`
### Running locally
You will need:
- Node 16 LTS
- npm
Copy `.github/workflows/support-files/.env.example` to `.github/workflows/support-files/.env` and valid Keybase credentials.
Then run `npm install` to get dependencies.
Start development mode for the notification type you want either by passing the value as an env var called `NYM_NOTIFICATION_KIND` or set the `.env` file values correctly.
```bash
cd .github/workflows/support-files
npm install
cp .env.example .env
vi .env
npm run dev
```
-1
View File
@@ -1 +0,0 @@
require('./notifications/send_message');
@@ -0,0 +1,2 @@
node_modules
.idea
@@ -4,14 +4,11 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "node dev.js", "format": "prettier --write send_message.js"
"format": "prettier --write **/*.js"
}, },
"dependencies": { "dependencies": {
"dotenv": "^16.0.0",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"keybase-bot": "^3.6.1", "keybase-bot": "^3.6.1"
"octokit": "^1.7.1"
}, },
"devDependencies": { "devDependencies": {
"prettier": "2.3.2" "prettier": "2.3.2"
@@ -0,0 +1,69 @@
const Bot = require('keybase-bot');
const Handlebars = require('handlebars');
const fs = require('fs');
async function main() {
const data = { env: process.env };
// const data = { ...PASTE TEST DATA HERE ... }; // -- DEV: uncomment to set test data
// validation of environment
if(!(process.env.NYM_PROJECT_NAME || data.env.NYM_PROJECT_NAME)) {
throw new Error('Please set env var NYM_PROJECT_NAME with the project name for displaying in notification messages');
}
const keybaseChannel = process.env.KEYBASE_NYM_CHANNEL || data.env.KEYBASE_NYM_CHANNEL;
if(!keybaseChannel) {
throw new Error('Please set env var KEYBASE_NYM_CHANNEL with the channel name for the notification message');
}
// extract the git branch name
const GIT_BRANCH_NAME = (process.env.GITHUB_REF || data.env.GITHUB_REF).split('/').slice(2).join('/');
data.env.GIT_BRANCH_NAME = GIT_BRANCH_NAME;
const source = fs
.readFileSync(process.env.IS_SUCCESS === 'true' ? 'success' : 'failure')
.toString();
const template = Handlebars.compile(source);
const result = template(data);
// -- DEV: uncomment to show what is available in the handlebars template / show the result
// console.dir({ data }, { depth: null });
// console.log(result);
const bot = new Bot();
try {
const username = process.env.KEYBASE_NYMBOT_USERNAME;
const paperkey = process.env.KEYBASE_NYMBOT_PAPERKEY;
if(!username) {
throw new Error('Username is not defined. Please set env var KEYBASE_NYMBOT_USERNAME');
}
if(!paperkey) {
throw new Error('Paperkey is not defined. Please set env var KEYBASE_NYMBOT_PAPERKEY');
}
console.log(`Initialising keybase with user "${username}" and key: "${'*'.repeat(paperkey.length)}"...`);
await bot.init(username, paperkey, { verbose: false });
const channel = {
name: 'nymtech_bot',
membersType: 'team',
topicName: keybaseChannel,
topic_type: 'CHAT',
};
const message = {
body: result,
};
console.log(`Sending to ${channel.name}#${channel.topicName}...`);
await bot.chat.send(channel, message);
console.log('Message sent!');
} catch (error) {
console.error(error);
process.exitCode = -1;
} finally {
await bot.deinit();
}
}
main();
@@ -1,11 +1,11 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View output:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/ > :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View output:** https://{{ env.GITHUB_REF_SLUG }}.{{ env.NYM_CI_WWW_BASE }}/
> ✅ **SUCCESS** > ✅ **SUCCESS**
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }} > `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }} > `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }} > `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}: Commit message:
``` ```
{{ env.GIT_COMMIT_MESSAGE }} {{ env.GIT_COMMIT_MESSAGE }}
``` ```
@@ -1,29 +0,0 @@
const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');
async function addToContextAndValidate(context) {
if (!context.env.NYM_CI_WWW_LOCATION) {
throw new Error('Please ensure the env var NYM_CI_WWW_LOCATION is set');
}
if (!context.env.NYM_CI_WWW_BASE) {
throw new Error('Please ensure the env var NYM_CI_WWW_BASE is set');
}
}
async function getMessageBody(context) {
const source = fs
.readFileSync(
context.env.IS_SUCCESS === 'true'
? path.resolve(__dirname, 'templates', 'success')
: path.resolve(__dirname, 'templates', 'failure'),
)
.toString();
const template = Handlebars.compile(source);
return template(context);
}
module.exports = {
addToContextAndValidate,
getMessageBody,
};
@@ -1,162 +0,0 @@
const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');
const { Octokit, App } = require('octokit');
async function addToContextAndValidate(context) {
if (!context.env.WORKFLOW_CONCLUSION) {
throw new Error('Please ensure the env var WORKFLOW_CONCLUSION is set');
}
if (!context.env.GITHUB_TOKEN) {
throw new Error('Please ensure the env var GITHUB_TOKEN is set');
}
if (!context.env.GITHUB_RUN_ID) {
throw new Error('Please ensure the env var GITHUB_RUN_ID is set');
}
if (!context.env.GITHUB_REPOSITORY) {
throw new Error('Please ensure the env var GITHUB_REPOSITORY is set');
}
}
async function getMessageBody(context) {
const source = fs
.readFileSync(
context.env.WORKFLOW_CONCLUSION === 'success'
? path.resolve(__dirname, 'templates', 'success')
: path.resolve(__dirname, 'templates', 'failure'),
)
.toString();
const template = Handlebars.compile(source);
// get job details from GitHub API
const octokit = new Octokit({ auth: context.env.GITHUB_TOKEN });
const [owner, repo] = context.env.GITHUB_REPOSITORY.split('/');
const {
data: { jobs },
} = await octokit.rest.actions.listJobsForWorkflowRun({
run_id: context.env.GITHUB_RUN_ID,
owner,
repo,
});
// uncomment this to see what is available for each job
if(process.env.SHOW_DEBUG) {
console.dir(jobs, { depth: null });
}
/*
a sample of the response is:
{
total_count: 10,
jobs: [
{
id: 5182940024,
run_id: 1840752095,
run_url: 'https://api.github.com/repos/nymtech/nym/actions/runs/1840752095',
run_attempt: 1,
node_id: 'CR_kwDODdjOis8AAAABNO1jeA',
head_sha: 'aa00eb70d57751bfa556bd3602df87c7473367fc',
url: 'https://api.github.com/repos/nymtech/nym/actions/jobs/5182940024',
html_url: 'https://github.com/nymtech/nym/runs/5182940024?check_suite_focus=true',
status: 'completed',
conclusion: 'success',
started_at: '2022-02-14T11:28:34Z',
completed_at: '2022-02-14T11:28:38Z',
name: 'matrix_prep',
steps: [
{
name: 'Set up job',
status: 'completed',
conclusion: 'success',
number: 1,
started_at: '2022-02-14T13:28:34.000+02:00',
completed_at: '2022-02-14T13:28:36.000+02:00'
},
{
name: 'Run actions/checkout@v2',
status: 'completed',
conclusion: 'success',
number: 2,
started_at: '2022-02-14T13:28:36.000+02:00',
completed_at: '2022-02-14T13:28:37.000+02:00'
},
...
],
check_run_url: 'https://api.github.com/repos/nymtech/nym/check-runs/5182940024',
labels: [ 'ubuntu-latest' ],
runner_id: 1,
runner_name: 'Hosted Agent',
runner_group_id: 2,
runner_group_name: 'GitHub Actions'
},
{
id: 5182943473,
run_id: 1840752095,
run_url: 'https://api.github.com/repos/nymtech/nym/actions/runs/1840752095',
run_attempt: 1,
node_id: 'CR_kwDODdjOis8AAAABNO1w8Q',
head_sha: 'aa00eb70d57751bfa556bd3602df87c7473367fc',
url: 'https://api.github.com/repos/nymtech/nym/actions/jobs/5182943473',
html_url: 'https://github.com/nymtech/nym/runs/5182943473?check_suite_focus=true',
status: 'completed',
conclusion: 'failure',
started_at: '2022-02-14T11:29:04Z',
completed_at: '2022-02-14T11:55:45Z',
name: 'build (macos-latest, stable, schedule)',
steps: [
{
name: 'Set up job',
status: 'completed',
conclusion: 'success',
number: 1,
started_at: '2022-02-14T13:29:04.000+02:00',
completed_at: '2022-02-14T13:29:26.000+02:00'
},
{
name: 'Install Dependencies (Linux)',
status: 'completed',
conclusion: 'skipped',
number: 2,
started_at: '2022-02-14T13:29:26.000+02:00',
completed_at: '2022-02-14T13:29:26.000+02:00'
},
{
name: 'Keybase - Send Notification',
status: 'completed',
conclusion: 'failure',
number: 15,
started_at: '2022-02-14T13:55:44.000+02:00',
completed_at: '2022-02-14T13:55:44.000+02:00'
},
],
check_run_url: 'https://api.github.com/repos/nymtech/nym/check-runs/5182943473',
labels: [ 'macos-latest' ],
runner_id: 4,
runner_name: 'GitHub Actions 4',
runner_group_id: 2,
runner_group_name: 'GitHub Actions'
},
...
]
}
*/
const jobResults = jobs
.map((job) => {
const icon = job.conclusion === 'success' ? '🟩' : '🟥';
// each job is converted into formatted markdown text
return `${icon} ${job.conclusion}: ${job.name} - ${job.html_url}`;
})
// and join with newlines for display in the template
.join('\n');
return template({ ...context, jobResults });
}
module.exports = {
addToContextAndValidate,
getMessageBody,
};
@@ -1,9 +0,0 @@
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
> :rocket: {{ env.NYM_PROJECT_NAME }}
> 🔴 **FAILURE** :cry:
> `when` {{ timestamp }}
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
{{ jobResults }}
@@ -1,9 +0,0 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }}
> ✅ **SUCCESS**
> `when` {{ timestamp }}
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
{{ jobResults }}
@@ -1,153 +0,0 @@
require('dotenv').config();
const Bot = require('keybase-bot');
let context = {
kinds: ['network-explorer', 'nightly'],
};
/**
* Validate that all required env and context vars are available
*/
function validateContext() {
if (!context.env.NYM_NOTIFICATION_KIND) {
throw new Error(
'Please set env var NYM_NOTIFICATION_KIND with the project kind that matches a directory in ".github/workflows/support-files"',
);
}
if (!context.kinds.includes(context.env.NYM_NOTIFICATION_KIND)) {
throw new Error(`Env var NYM_NOTIFICATION_KIND is not in ${context.kinds}`);
}
if (!context.env.NYM_PROJECT_NAME) {
throw new Error(
'Please set env var NYM_PROJECT_NAME with the project name for displaying in notification messages',
);
}
if (!context.env.KEYBASE_NYM_CHANNEL) {
throw new Error(
'Please set env var KEYBASE_NYM_CHANNEL with the channel name for the notification message',
);
}
if (!context.env.KEYBASE_NYMBOT_USERNAME) {
throw new Error(
'Username is not defined. Please set env var KEYBASE_NYMBOT_USERNAME',
);
}
if (!context.env.KEYBASE_NYMBOT_PAPERKEY) {
throw new Error(
'Paperkey is not defined. Please set env var KEYBASE_NYMBOT_PAPERKEY',
);
}
}
/**
* Creates a context that will be available in the templates for rendering notifications
*/
function createTemplateContext() {
const options = { dateStyle: 'full', timeStyle: 'long' };
context.timestamp = new Date().toLocaleString(undefined, options);
// add environment to template context and validate
context.env = process.env;
try {
validateContext();
} catch (e) {
if(process.env.SHOW_DEBUG) {
// recursively print the context for easy debugging and rethrow the error
console.dir({ context }, { depth: null });
}
throw e;
}
context.kind = context.env.NYM_NOTIFICATION_KIND;
context.keybase = {
channel: context.env.KEYBASE_NYM_CHANNEL,
username: context.env.KEYBASE_NYMBOT_USERNAME,
paperkey: context.env.KEYBASE_NYMBOT_PAPERKEY,
};
if (!context.env.GIT_BRANCH_NAME) {
context.env.GIT_BRANCH_NAME = context.env.GITHUB_REF.split('/')
.slice(2)
.join('/');
}
context.status = process.env.IS_SUCCESS === 'true' ? 'success' : 'failure';
}
async function sendKeybaseMessage(messageBody) {
const bot = new Bot();
try {
console.log(
`Initialising keybase with user "${
context.keybase.username
}" and key: "${'*'.repeat(context.keybase.paperkey.length)}"...`,
);
await bot.init(context.keybase.username, context.keybase.paperkey, {
verbose: false,
});
const channel = {
name: 'nymtech_bot',
membersType: 'team',
topicName: context.keybase.channel,
topic_type: 'CHAT',
};
const message = {
body: messageBody,
};
console.log(`Sending to ${channel.name}#${channel.topicName}...`);
await bot.chat.send(channel, message);
console.log('Message sent!');
} catch (error) {
console.error(error);
process.exitCode = -1;
} finally {
await bot.deinit();
}
}
/**
* Uses the `kind` set in the context to process the context and generate a notification message
* @returns {Promise<string>} A string notification message body
*/
async function processKindScript() {
const script = require(`../${context.kind}`);
if (!script.addToContextAndValidate) {
throw new Error(
`"./${context.kind}/index.js" does not export a method called "async addToContextAndValidate(context)"`,
);
}
if (!script.getMessageBody) {
throw new Error(
`"./${context.kind}/index.js" does not export a method called "async getMessageBody(context)"`,
);
}
// call the script to modify and validate the context
await script.addToContextAndValidate(context);
// let the script create a message body and return the result as a string for sending
return await script.getMessageBody(context);
}
/**
* The main function, as async so that await syntax is available
*/
async function main() {
createTemplateContext();
console.log(`Sending notification for kind "${context.kind}"...`);
const messageBody = await processKindScript();
if(process.env.SHOW_DEBUG) {
console.log('-----------------------------------------');
console.log(messageBody);
console.log('-----------------------------------------');
}
await sendKeybaseMessage(messageBody);
}
// call main function and let NodeJS handle the promise
main();
+22
View File
@@ -0,0 +1,22 @@
name: Generate TS types
on: push
jobs:
tauri-wallet-types:
runs-on: ubuntu-latest
steps:
- name: Prepare
run: sudo apt-get update && sudo apt-get install -y libpango1.0-dev libatk1.0-dev libgdk-pixbuf2.0-dev libsoup2.4-dev librust-gdk-dev libwebkit2gtk-4.0-dev
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Generate TS
run: cd tauri-wallet/src-tauri && cargo test
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
with:
add: '["tauri-wallet"]'
message: '[ci skip] Generate TS types'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+11 -12
View File
@@ -1,9 +1,6 @@
name: Wasm Client name: Wasm Client
on: on: [push, pull_request]
pull_request:
paths-ignore:
- 'explorer/**'
jobs: jobs:
wasm: wasm:
@@ -29,17 +26,19 @@ jobs:
command: build command: build
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --features=coconut args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --features=coconut
- uses: actions-rs/cargo@v1 # for some reason this does not seem to work correctly, leave it for later, building is good enough for now
with: # - uses: actions-rs/cargo@v1
command: test # with:
args: --manifest-path clients/webassembly/Cargo.toml # command: test
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
with: with:
command: fmt command: fmt
args: --manifest-path clients/webassembly/Cargo.toml -- --check args: --manifest-path clients/webassembly/Cargo.toml -- --check
- uses: actions-rs/cargo@v1 # for some reason this does not seem to work correctly, leave it for later, building is good enough for now
with: # - uses: actions-rs/cargo@v1
command: clippy # with:
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings # command: clippy
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
-3
View File
@@ -1,3 +0,0 @@
unreleased=true
future-release=v0.12.0
since-tag=v0.11.0
+1 -7
View File
@@ -11,6 +11,7 @@ target
/.vscode/settings.json /.vscode/settings.json
validator/.vscode validator/.vscode
sample-configs/validator-config.toml sample-configs/validator-config.toml
.vscode
scripts/deploy_qa.sh scripts/deploy_qa.sh
scripts/run_gate.sh scripts/run_gate.sh
scripts/run_mix.sh scripts/run_mix.sh
@@ -24,15 +25,8 @@ v6-topology.json
/explorer/downloads/topology.json /explorer/downloads/topology.json
/explorer/public/downloads/mixmining.json /explorer/public/downloads/mixmining.json
/explorer/public/downloads/topology.json /explorer/public/downloads/topology.json
/nym-wallet/dist/*
/clients/validator/examples/nym-driver-example/current-contract.txt /clients/validator/examples/nym-driver-example/current-contract.txt
validator-api/v4.json validator-api/v4.json
validator-api/v6.json validator-api/v6.json
**/node_modules **/node_modules
validator-api/keypair validator-api/keypair
contracts/mixnet/code_id
contracts/mixnet/Justfile
contracts/mixnet/Makefile
validator-config
*.patch
validator-api-config.toml
-1
View File
@@ -1 +0,0 @@
2.7.5
+879 -228
View File
File diff suppressed because it is too large Load Diff
Generated
+292 -1363
View File
File diff suppressed because it is too large Load Diff
+4 -10
View File
@@ -4,20 +4,19 @@
[profile.release] [profile.release]
panic = "abort" panic = "abort"
opt-level = "s" opt-level = "s"
overflow-checks = true
[profile.dev] [profile.dev]
panic = "abort" panic = "abort"
[workspace] [workspace]
resolver = "2"
members = [ members = [
"clients/client-core", "clients/client-core",
"clients/native", "clients/native",
"clients/native/websocket-requests", "clients/native/websocket-requests",
"clients/socks5", "clients/socks5",
"clients/tauri-client/src-tauri", "clients/tauri-client/src-tauri",
"clients/webassembly",
"common/client-libs/gateway-client", "common/client-libs/gateway-client",
"common/client-libs/mixnet-client", "common/client-libs/mixnet-client",
"common/client-libs/validator-client", "common/client-libs/validator-client",
@@ -25,14 +24,10 @@ members = [
"common/config", "common/config",
"common/credentials", "common/credentials",
"common/crypto", "common/crypto",
"common/bandwidth-claim-contract", "common/mixnet-contract",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/mixnode-common", "common/mixnode-common",
"common/network-defaults", "common/network-defaults",
"common/nonexhaustive-delayqueue", "common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nymsphinx", "common/nymsphinx",
"common/nymsphinx/acknowledgements", "common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing", "common/nymsphinx/addressing",
@@ -54,17 +49,16 @@ members = [
"mixnode", "mixnode",
"service-providers/network-requester", "service-providers/network-requester",
"validator-api", "validator-api",
"validator-api/validator-api-requests",
] ]
default-members = [ default-members = [
"clients/native", "clients/native",
"clients/socks5", "clients/socks5",
# "clients/webassembly",
"gateway", "gateway",
"service-providers/network-requester", "service-providers/network-requester",
"mixnode", "mixnode",
"validator-api", "validator-api",
"explorer-api",
] ]
exclude = ["explorer", "contracts", "tokenomics-py", "clients/webassembly"] exclude = ["explorer", "contracts"]
-55
View File
@@ -1,55 +0,0 @@
test: build clippy-all cargo-test wasm fmt
happy: fmt clippy-happy test
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet
cargo-test: test-main test-contracts test-wallet
build: build-main build-contracts build-wallet
fmt: fmt-main fmt-contracts fmt-wallet
clippy-happy-main:
cargo clippy
clippy-happy-contracts:
cargo clippy --manifest-path contracts/Cargo.toml --target wasm32-unknown-unknown
clippy-happy-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml
clippy-all-main:
cargo clippy --all-features -- -D warnings
clippy-all-contracts:
cargo clippy --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
clippy-all-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
test-main:
cargo test --all-features
test-contracts:
cargo test --manifest-path contracts/Cargo.toml --all-features
test-wallet:
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
build-main:
cargo build --all
build-contracts:
cargo build --manifest-path contracts/Cargo.toml --all
build-wallet:
cargo build --manifest-path nym-wallet/Cargo.toml --all
fmt-main:
cargo fmt --all
fmt-contracts:
cargo fmt --manifest-path contracts/Cargo.toml --all
fmt-wallet:
cargo fmt --manifest-path nym-wallet/Cargo.toml --all
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
+4 -42
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: Apache-2.0
## The Nym Privacy Platform ## The Nym Privacy Platform
This repository contains the Nym mixnet.
The platform is composed of multiple Rust crates. Top-level executable binary crates include: The platform is composed of multiple Rust crates. Top-level executable binary crates include:
* nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers. * nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
@@ -13,7 +15,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices. * nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices.
* nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing"). * nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
* nym-explorer - a (projected) block explorer and (existing) mixnet viewer. * nym-explorer - a (projected) block explorer and (existing) mixnet viewer.
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework. * nym-wallet (currently in development)- a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0)
[![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop) [![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
@@ -21,8 +23,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
### Building ### Building
Platform build instructions are available on [our docs site](https://nymtech.net/docs/stable/run-nym-nodes/build-nym). Platform build instructions are available on [our docs site](https://nymtech.net/docs/0.11.0/overview/index/).
Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/stable/nym-apps/wallet#for-developers).
### Developing ### Developing
@@ -32,45 +33,6 @@ There's a `.env.sample-dev` file provided which you can rename to `.env` if you
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel. You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
### Rewards
Node, node operator and delegator rewards are determined according to the principles laid out in the section 6 of [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf). Below is a TLDR of the variables and formulas involved in calculating the epoch rewards. Initial reward pool is set to 250 million Nym, making the circulating supply 750 million Nym.
|Symbol|Definition|
|---|---|
|<img src="https://render.githubusercontent.com/render/math?math=R">|global share of rewards available, starts at 2% of the reward pool.
|<img src="https://render.githubusercontent.com/render/math?math=R_{i}">|node reward for mixnode `i`.
|<img src="https://render.githubusercontent.com/render/math?math=\sigma_{i}">|ratio of total node stake (node bond + all delegations) to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}">|ratio of stake operator has pledged to their node to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}">|fraction of total effort undertaken by node `i`, set to `1/k`.
|<img src="https://render.githubusercontent.com/render/math?math=k">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `reward set` size, and set to 720 in testnet Sandbox.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}">|declared profit margin of operator `i`, defaults to 10% in.
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}">|cost of operating node `i` for the duration of the rewarding eopoch, set to 40 NYMT.
Node reward for node `i` is determined as:
<img src="https://render.githubusercontent.com/render/math?math=R_{i}=PF_{i} \cdot R \cdot (\sigma^'_{i} \cdot \omega_{i} \cdot k %2b \alpha \cdot \lambda^'_{i} \cdot \sigma^'_{i} \cdot k)/(1 %2b \alpha)">
where:
<img src="https://render.githubusercontent.com/render/math?math=\sigma^'_{i} = min\{\sigma_{i}, 1/k\}">
and
<img src="https://render.githubusercontent.com/render/math?math=\lambda^'_{i} = min\{\lambda_{i}, 1/k\}">
Operator of node `i` is credited with the following amount:
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}">
Delegate with stake `s` recieves:
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}">
where `s'` is stake `s` scaled over total token circulating supply.
### Licensing and copyright information ### Licensing and copyright information
This program is available as open source under the terms of the Apache 2.0 license. However, some elements are being licensed under CC0-1.0 and MIT. For accurate information, please check individual files. This program is available as open source under the terms of the Apache 2.0 license. However, some elements are being licensed under CC0-1.0 and MIT. For accurate information, please check individual files.
+2 -5
View File
@@ -1,8 +1,8 @@
[package] [package]
name = "client-core" name = "client-core"
version = "0.12.0" version = "0.11.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -30,6 +30,3 @@ validator-client = { path = "../../common/client-libs/validator-client" }
[dev-dependencies] [dev-dependencies]
tempfile = "3.1.0" tempfile = "3.1.0"
[features]
coconut = []
@@ -13,6 +13,7 @@ use nymsphinx::utils::sample_poisson_duration;
use rand::{rngs::OsRng, CryptoRng, Rng}; use rand::{rngs::OsRng, CryptoRng, Rng};
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use tokio::runtime::Handle;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use tokio::time; use tokio::time;
@@ -164,8 +165,8 @@ impl LoopCoverTrafficStream<OsRng> {
} }
} }
pub fn start(mut self) -> JoinHandle<()> { pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
tokio::spawn(async move { handle.spawn(async move {
self.run().await; self.run().await;
}) })
} }
@@ -79,9 +79,9 @@ impl KeyManager {
))?; ))?;
let gateway_shared_key: SharedKeys = let gateway_shared_key: SharedKeys =
pemstore::load_key(client_pathfinder.gateway_shared_key())?; pemstore::load_key(&client_pathfinder.gateway_shared_key().to_owned())?;
let ack_key: AckKey = pemstore::load_key(client_pathfinder.ack_key())?; let ack_key: AckKey = pemstore::load_key(&client_pathfinder.ack_key().to_owned())?;
// TODO: ack key is never stored so it is generated now. But perhaps it should be stored // TODO: ack key is never stored so it is generated now. But perhaps it should be stored
// after all for consistency sake? // after all for consistency sake?
@@ -6,6 +6,7 @@ use futures::StreamExt;
use gateway_client::GatewayClient; use gateway_client::GatewayClient;
use log::*; use log::*;
use nymsphinx::forwarding::packet::MixPacket; use nymsphinx::forwarding::packet::MixPacket;
use tokio::runtime::Handle;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>; pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
@@ -71,8 +72,8 @@ impl MixTrafficController {
} }
} }
pub fn start(mut self) -> JoinHandle<()> { pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
tokio::spawn(async move { handle.spawn(async move {
self.run().await; self.run().await;
}) })
} }
@@ -22,6 +22,7 @@ use nymsphinx::addressing::clients::Recipient;
use rand::{rngs::OsRng, CryptoRng, Rng}; use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::runtime::Handle;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
mod acknowledgement_control; mod acknowledgement_control;
@@ -169,8 +170,10 @@ impl RealMessagesController<OsRng> {
self.ack_control = Some(ack_control_fut.await.unwrap()); self.ack_control = Some(ack_control_fut.await.unwrap());
} }
pub fn start(mut self) -> JoinHandle<Self> { // &Handle is only passed for consistency sake with other client modules, but I think
tokio::spawn(async move { // when we get to refactoring, we should apply gateway approach and make it implicit
pub fn start(mut self, handle: &Handle) -> JoinHandle<Self> {
handle.spawn(async move {
self.run().await; self.run().await;
self self
}) })
@@ -15,6 +15,7 @@ use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorith
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage}; use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
use tokio::runtime::Handle;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
// Buffer Requests to say "hey, send any reconstructed messages to this channel" // Buffer Requests to say "hey, send any reconstructed messages to this channel"
@@ -290,8 +291,8 @@ impl RequestReceiver {
} }
} }
fn start(mut self) -> JoinHandle<()> { fn start(mut self, handle: &Handle) -> JoinHandle<()> {
tokio::spawn(async move { handle.spawn(async move {
while let Some(request) = self.query_receiver.next().await { while let Some(request) = self.query_receiver.next().await {
match request { match request {
ReceivedBufferMessage::ReceiverAnnounce(sender) => { ReceivedBufferMessage::ReceiverAnnounce(sender) => {
@@ -321,8 +322,8 @@ impl FragmentedMessageReceiver {
mixnet_packet_receiver, mixnet_packet_receiver,
} }
} }
fn start(mut self) -> JoinHandle<()> { fn start(mut self, handle: &Handle) -> JoinHandle<()> {
tokio::spawn(async move { handle.spawn(async move {
while let Some(new_messages) = self.mixnet_packet_receiver.next().await { while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
self.received_buffer.handle_new_received(new_messages).await; self.received_buffer.handle_new_received(new_messages).await;
} }
@@ -354,9 +355,9 @@ impl ReceivedMessagesBufferController {
} }
} }
pub fn start(self) { pub fn start(self, handle: &Handle) {
// TODO: should we do anything with JoinHandle(s) returned by start methods? // TODO: should we do anything with JoinHandle(s) returned by start methods?
self.fragmented_message_receiver.start(); self.fragmented_message_receiver.start(handle);
self.request_receiver.start(); self.request_receiver.start(handle);
} }
} }
@@ -59,7 +59,7 @@ impl ReplyKeyStorage {
) -> Result<(), ReplyKeyStorageError> { ) -> Result<(), ReplyKeyStorageError> {
let digest = encryption_key.compute_digest(); let digest = encryption_key.compute_digest();
let insertion_result = match self.db.insert(digest, encryption_key.to_bytes()) { let insertion_result = match self.db.insert(digest.to_vec(), encryption_key.to_bytes()) {
Err(e) => Err(ReplyKeyStorageError::DbWriteError(e)), Err(e) => Err(ReplyKeyStorageError::DbWriteError(e)),
Ok(existing_key) => { Ok(existing_key) => {
if existing_key.is_some() { if existing_key.is_some() {
@@ -79,7 +79,7 @@ impl ReplyKeyStorage {
&self, &self,
key_digest: EncryptionKeyDigest, key_digest: EncryptionKeyDigest,
) -> Result<Option<SurbEncryptionKey>, ReplyKeyStorageError> { ) -> Result<Option<SurbEncryptionKey>, ReplyKeyStorageError> {
let removal_result = match self.db.remove(key_digest) { let removal_result = match self.db.remove(&key_digest.to_vec()) {
Err(e) => Err(ReplyKeyStorageError::DbReadError(e)), Err(e) => Err(ReplyKeyStorageError::DbReadError(e)),
Ok(existing_key) => { Ok(existing_key) => {
Ok(existing_key.map(|existing_key| self.read_encryption_key(existing_key))) Ok(existing_key.map(|existing_key| self.read_encryption_key(existing_key)))
@@ -10,6 +10,7 @@ use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::time; use std::time;
use std::time::Duration; use std::time::Duration;
use tokio::runtime::Handle;
use tokio::sync::{RwLock, RwLockReadGuard}; use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use topology::{nym_topology_from_bonds, NymTopology}; use topology::{nym_topology_from_bonds, NymTopology};
@@ -256,7 +257,7 @@ impl TopologyRefresher {
Ok(mixes) => mixes, Ok(mixes) => mixes,
}; };
let gateways = match self.validator_client.get_cached_gateways().await { let gateways = match self.validator_client.get_cached_active_gateways().await {
Err(err) => { Err(err) => {
error!("failed to get network gateways - {}", err); error!("failed to get network gateways - {}", err);
return None; return None;
@@ -303,8 +304,8 @@ impl TopologyRefresher {
self.topology_accessor.is_routable().await self.topology_accessor.is_routable().await
} }
pub fn start(mut self) -> JoinHandle<()> { pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
tokio::spawn(async move { handle.spawn(async move {
loop { loop {
tokio::time::sleep(self.refresh_rate).await; tokio::time::sleep(self.refresh_rate).await;
self.refresh().await; self.refresh().await;
+13 -107
View File
@@ -22,10 +22,7 @@ const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20)
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50); const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000); const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
// bandwidth bridging protocol, we can come back to a smaller timeout value
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
pub fn missing_string_value() -> String { pub fn missing_string_value() -> String {
MISSING_VALUE.to_string() MISSING_VALUE.to_string()
@@ -103,44 +100,15 @@ impl<T: NymConfig> Config<T> {
self::Client::<T>::default_reply_encryption_key_store_path(&id); self::Client::<T>::default_reply_encryption_key_store_path(&id);
} }
#[cfg(not(feature = "coconut"))]
if self
.client
.backup_bandwidth_token_keys_dir
.as_os_str()
.is_empty()
{
self.client.backup_bandwidth_token_keys_dir =
self::Client::<T>::default_backup_bandwidth_token_keys_dir(&id);
}
self.client.id = id; self.client.id = id;
} }
pub fn with_testnet_mode(&mut self, testnet_mode: bool) {
self.client.testnet_mode = testnet_mode;
}
pub fn with_gateway_endpoint<S: Into<String>>(&mut self, id: S, owner: S, listener: S) {
self.client.gateway_endpoint = GatewayEndpoint {
gateway_id: id.into(),
gateway_owner: owner.into(),
gateway_listener: listener.into(),
};
}
pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) { pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) {
self.client.gateway_endpoint.gateway_id = id.into(); self.client.gateway_id = id.into();
} }
#[cfg(not(feature = "coconut"))] pub fn with_gateway_listener<S: Into<String>>(&mut self, gateway_listener: S) {
pub fn with_eth_private_key<S: Into<String>>(&mut self, eth_private_key: S) { self.client.gateway_listener = gateway_listener.into();
self.client.eth_private_key = eth_private_key.into();
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_endpoint<S: Into<String>>(&mut self, eth_endpoint: S) {
self.client.eth_endpoint = eth_endpoint.into();
} }
pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec<Url>) { pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec<Url>) {
@@ -161,10 +129,6 @@ impl<T: NymConfig> Config<T> {
self.client.id.clone() self.client.id.clone()
} }
pub fn get_testnet_mode(&self) -> bool {
self.client.testnet_mode
}
pub fn get_nym_root_directory(&self) -> PathBuf { pub fn get_nym_root_directory(&self) -> PathBuf {
self.client.nym_root_directory.clone() self.client.nym_root_directory.clone()
} }
@@ -202,30 +166,11 @@ impl<T: NymConfig> Config<T> {
} }
pub fn get_gateway_id(&self) -> String { pub fn get_gateway_id(&self) -> String {
self.client.gateway_endpoint.gateway_id.clone() self.client.gateway_id.clone()
}
pub fn get_gateway_owner(&self) -> String {
self.client.gateway_endpoint.gateway_owner.clone()
} }
pub fn get_gateway_listener(&self) -> String { pub fn get_gateway_listener(&self) -> String {
self.client.gateway_endpoint.gateway_listener.clone() self.client.gateway_listener.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_backup_bandwidth_token_keys_dir(&self) -> PathBuf {
self.client.backup_bandwidth_token_keys_dir.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_endpoint(&self) -> String {
self.client.eth_endpoint.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_private_key(&self) -> String {
self.client.eth_private_key.clone()
} }
// Debug getters // Debug getters
@@ -280,19 +225,6 @@ impl<T: NymConfig> Default for Config<T> {
} }
} }
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
struct GatewayEndpoint {
/// gateway_id specifies ID of the gateway to which the client should send messages.
/// If initially omitted, a random gateway will be chosen from the available topology.
gateway_id: String,
/// Address of the gateway owner to which the client should send messages.
gateway_owner: String,
/// Address of the gateway listener to which all client requests should be sent.
gateway_listener: String,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)] #[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct Client<T> { pub struct Client<T> {
/// Version of the client for which this configuration was created. /// Version of the client for which this configuration was created.
@@ -302,11 +234,6 @@ pub struct Client<T> {
/// ID specifies the human readable ID of this particular client. /// ID specifies the human readable ID of this particular client.
id: String, id: String,
/// Indicates whether this client is running in a testnet mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials.
#[serde(default)]
testnet_mode: bool,
/// Addresses to APIs running on validator from which the client gets the view of the network. /// Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls: Vec<Url>, validator_api_urls: Vec<Url>,
@@ -334,22 +261,12 @@ pub struct Client<T> {
/// sent but not received back. /// sent but not received back.
reply_encryption_key_store_path: PathBuf, reply_encryption_key_store_path: PathBuf,
/// Information regarding how the client should send data to gateway. /// gateway_id specifies ID of the gateway to which the client should send messages.
gateway_endpoint: GatewayEndpoint, /// If initially omitted, a random gateway will be chosen from the available topology.
gateway_id: String,
/// Path to directory containing public/private keys used for bandwidth token purchase. /// Address of the gateway listener to which all client requests should be sent.
/// Those are saved in case of emergency, to be able to reclaim bandwidth tokens. gateway_listener: String,
/// The public key is the name of the file, while the private key is the content.
#[cfg(not(feature = "coconut"))]
backup_bandwidth_token_keys_dir: PathBuf,
/// Ethereum private key.
#[cfg(not(feature = "coconut"))]
eth_private_key: String,
/// Address to an Ethereum full node.
#[cfg(not(feature = "coconut"))]
eth_endpoint: String,
/// nym_home_directory specifies absolute path to the home nym Clients directory. /// nym_home_directory specifies absolute path to the home nym Clients directory.
/// It is expected to use default value and hence .toml file should not redefine this field. /// It is expected to use default value and hence .toml file should not redefine this field.
@@ -365,7 +282,6 @@ impl<T: NymConfig> Default for Client<T> {
Client { Client {
version: env!("CARGO_PKG_VERSION").to_string(), version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(), id: "".to_string(),
testnet_mode: false,
validator_api_urls: default_api_endpoints(), validator_api_urls: default_api_endpoints(),
private_identity_key_file: Default::default(), private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(), public_identity_key_file: Default::default(),
@@ -374,13 +290,8 @@ impl<T: NymConfig> Default for Client<T> {
gateway_shared_key_file: Default::default(), gateway_shared_key_file: Default::default(),
ack_key_file: Default::default(), ack_key_file: Default::default(),
reply_encryption_key_store_path: Default::default(), reply_encryption_key_store_path: Default::default(),
gateway_endpoint: Default::default(), gateway_id: "".to_string(),
#[cfg(not(feature = "coconut"))] gateway_listener: "".to_string(),
backup_bandwidth_token_keys_dir: Default::default(),
#[cfg(not(feature = "coconut"))]
eth_private_key: "".to_string(),
#[cfg(not(feature = "coconut"))]
eth_endpoint: "".to_string(),
nym_root_directory: T::default_root_directory(), nym_root_directory: T::default_root_directory(),
super_struct: Default::default(), super_struct: Default::default(),
} }
@@ -415,11 +326,6 @@ impl<T: NymConfig> Client<T> {
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf { fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("reply_key_store") T::default_data_directory(Some(id)).join("reply_key_store")
} }
#[cfg(not(feature = "coconut"))]
fn default_backup_bandwidth_token_keys_dir(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("backup_bandwidth_token_keys")
}
} }
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] #[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
+3 -9
View File
@@ -1,9 +1,8 @@
[package] [package]
name = "nym-client" name = "nym-client"
version = "0.12.1" version = "0.11.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021" edition = "2018"
rust-version = "1.56"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -24,7 +23,7 @@ dirs = "3.0" # for determining default store directories in config
dotenv = "0.15.0" # for obtaining environmental variables (only used for RUST_LOG for time being) dotenv = "0.15.0" # for obtaining environmental variables (only used for RUST_LOG for time being)
log = "0.4" # self explanatory log = "0.4" # self explanatory
pretty_env_logger = "0.4" # for formatting log messages pretty_env_logger = "0.4" # for formatting log messages
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use rand = {version = "0.7.3", features = ["wasm-bindgen"]} # rng-related traits + some rng implementation to use
serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
sled = "0.34" # for storage of replySURB decryption keys sled = "0.34" # for storage of replySURB decryption keys
tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal"] } # async runtime tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal"] } # async runtime
@@ -44,14 +43,9 @@ topology = { path = "../../common/topology" }
websocket-requests = { path = "websocket-requests" } websocket-requests = { path = "websocket-requests" }
validator-client = { path = "../../common/client-libs/validator-client" } validator-client = { path = "../../common/client-libs/validator-client" }
version-checker = { path = "../../common/version-checker" } version-checker = { path = "../../common/version-checker" }
network-defaults = { path = "../../common/network-defaults" }
[features] [features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"] coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
eth = []
[dev-dependencies] [dev-dependencies]
serde_json = "1.0" # for the "textsend" example serde_json = "1.0" # for the "textsend" example
[build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
-8
View File
@@ -1,8 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
fn main() {
vergen(Config::default()).expect("failed to extract build metadata")
}
@@ -3,5 +3,6 @@ module github.com/nymtech/nym/clients/native/examples/go
go 1.14 go 1.14
require ( require (
github.com/btcsuite/btcutil v1.0.2 // indirect
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
) )
@@ -2441,9 +2441,9 @@
} }
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.14.8", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -3806,9 +3806,9 @@
"optional": true "optional": true
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.2.0", "version": "3.1.23",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
"dev": true, "dev": true,
"peer": true, "peer": true,
"bin": { "bin": {
@@ -3943,9 +3943,9 @@
} }
}, },
"node_modules/nth-check": { "node_modules/nth-check": {
"version": "2.0.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
"integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==",
"dependencies": { "dependencies": {
"boolbase": "^1.0.0" "boolbase": "^1.0.0"
}, },
@@ -6085,9 +6085,9 @@
} }
}, },
"node_modules/url-parse": { "node_modules/url-parse": {
"version": "1.5.7", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==", "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"querystringify": "^2.1.1", "querystringify": "^2.1.1",
@@ -8853,9 +8853,9 @@
} }
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.14.8", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==",
"dev": true "dev": true
}, },
"for-in": { "for-in": {
@@ -9871,9 +9871,9 @@
"optional": true "optional": true
}, },
"nanoid": { "nanoid": {
"version": "3.2.0", "version": "3.1.23",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
"dev": true, "dev": true,
"peer": true "peer": true
}, },
@@ -9984,9 +9984,9 @@
} }
}, },
"nth-check": { "nth-check": {
"version": "2.0.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
"integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==",
"requires": { "requires": {
"boolbase": "^1.0.0" "boolbase": "^1.0.0"
} }
@@ -11733,9 +11733,9 @@
} }
}, },
"url-parse": { "url-parse": {
"version": "1.5.7", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
"integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==", "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"querystringify": "^2.1.1", "querystringify": "^2.1.1",
@@ -35,7 +35,7 @@ async fn send_file_with_reply() {
let (mut ws_stream, _) = connect_async(uri).await.unwrap(); let (mut ws_stream, _) = connect_async(uri).await.unwrap();
let recipient = get_self_address(&mut ws_stream).await; let recipient = get_self_address(&mut ws_stream).await;
println!("our full address is: {}", recipient); println!("our full address is: {}", recipient.to_string());
let read_data = std::fs::read("examples/dummy_file").unwrap(); let read_data = std::fs::read("examples/dummy_file").unwrap();
@@ -83,7 +83,7 @@ async fn send_file_without_reply() {
let (mut ws_stream, _) = connect_async(uri).await.unwrap(); let (mut ws_stream, _) = connect_async(uri).await.unwrap();
let recipient = get_self_address(&mut ws_stream).await; let recipient = get_self_address(&mut ws_stream).await;
println!("our full address is: {}", recipient); println!("our full address is: {}", recipient.to_string());
let read_data = std::fs::read("examples/dummy_file").unwrap(); let read_data = std::fs::read("examples/dummy_file").unwrap();
@@ -36,7 +36,7 @@ async fn send_text_with_reply() {
let (mut ws_stream, _) = connect_async(uri).await.unwrap(); let (mut ws_stream, _) = connect_async(uri).await.unwrap();
let recipient = get_self_address(&mut ws_stream).await; let recipient = get_self_address(&mut ws_stream).await;
println!("our full address is: {}", recipient); println!("our full address is: {}", recipient.to_string());
let send_request = json!({ let send_request = json!({
"type" : "send", "type" : "send",
@@ -76,7 +76,7 @@ async fn send_text_without_reply() {
let (mut ws_stream, _) = connect_async(uri).await.unwrap(); let (mut ws_stream, _) = connect_async(uri).await.unwrap();
let recipient = get_self_address(&mut ws_stream).await; let recipient = get_self_address(&mut ws_stream).await;
println!("our full address is: {}", recipient); println!("our full address is: {}", recipient.to_string());
let send_request = json!({ let send_request = json!({
"type" : "send", "type" : "send",
+7 -27
View File
@@ -5,7 +5,7 @@ pub(crate) fn config_template() -> &'static str {
// While using normal toml marshalling would have been way simpler with less overhead, // While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of // I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields. // particular fields.
// Note: any changes to the template must be reflected in the appropriate structs. // Note: any changes to the template must be reflected in the appropriate structs in verloc.
r#" r#"
# This is a TOML config file. # This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml # For more information, see https://github.com/toml-lang/toml
@@ -19,10 +19,6 @@ version = '{{ client.version }}'
# Human readable ID of this particular client. # Human readable ID of this particular client.
id = '{{ client.id }}' id = '{{ client.id }}'
# Indicates whether this client is running in a testnet mode, thus attempting
# to claim bandwidth without presenting bandwidth credentials.
testnet_mode = {{ client.testnet_mode }}
# Addresses to APIs running on validator from which the client gets the view of the network. # Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [ validator_api_urls = [
{{#each client.validator_api_urls }} {{#each client.validator_api_urls }}
@@ -46,19 +42,14 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
# sent but not received back. # sent but not received back.
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}' reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to directory containing public/private keys used for bandwidth token purchase.
# Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
# The public key is the name of the file, while the private key is the content.
backup_bandwidth_token_keys_dir = '{{ client.backup_bandwidth_token_keys_dir }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options ##### ##### additional client config options #####
# ID of the gateway from which the client should be fetching messages.
gateway_id = '{{ client.gateway_id }}'
# Address of the gateway listener to which all client requests should be sent.
gateway_listener = '{{ client.gateway_listener }}'
# A gateway specific, optional, base58 stringified shared key used for # A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway. # communication with particular gateway.
gateway_shared_key_file = '{{ client.gateway_shared_key_file }}' gateway_shared_key_file = '{{ client.gateway_shared_key_file }}'
@@ -72,17 +63,6 @@ ack_key_file = '{{ client.ack_key_file }}'
# Absolute path to the home Nym Clients directory. # Absolute path to the home Nym Clients directory.
nym_root_directory = '{{ client.nym_root_directory }}' nym_root_directory = '{{ client.nym_root_directory }}'
[client.gateway_endpoint]
# ID of the gateway from which the client should be fetching messages.
gateway_id = '{{ client.gateway_endpoint.gateway_id }}'
# Address of the gateway owner to which the client should send messages.
gateway_owner = '{{ client.gateway_endpoint.gateway_owner }}'
# Address of the gateway listener to which all client requests should be sent.
gateway_listener = '{{ client.gateway_endpoint.gateway_listener }}'
##### socket config options ##### ##### socket config options #####
+86 -57
View File
@@ -1,6 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use crate::websocket;
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream; use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{ use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender, InputMessage, InputMessageReceiver, InputMessageSender,
@@ -22,7 +24,6 @@ use client_core::client::topology_control::{
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use crypto::asymmetric::identity; use crypto::asymmetric::identity;
use futures::channel::mpsc; use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{ use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver, AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender, MixnetMessageSender,
@@ -32,9 +33,12 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity; use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::anonymous_replies::ReplySurb; use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage; use nymsphinx::receiver::ReconstructedMessage;
use tokio::runtime::Runtime;
use crate::client::config::{Config, SocketType}; #[cfg(feature = "coconut")]
use crate::websocket; use coconut_interface::Credential;
#[cfg(feature = "coconut")]
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
pub(crate) mod config; pub(crate) mod config;
@@ -43,6 +47,11 @@ pub struct NymClient {
/// key filepaths, etc. /// key filepaths, etc.
config: Config, config: Config,
/// Tokio runtime used for futures execution.
// TODO: JS: Personally I think I prefer the implicit way of using it that we've done with the
// gateway.
runtime: Runtime,
/// KeyManager object containing smart pointers to all relevant keys used by the client. /// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager, key_manager: KeyManager,
@@ -62,6 +71,7 @@ impl NymClient {
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys"); let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
NymClient { NymClient {
runtime: Runtime::new().unwrap(),
config, config,
key_manager, key_manager,
input_tx: None, input_tx: None,
@@ -87,6 +97,9 @@ impl NymClient {
mix_tx: BatchMixMessageSender, mix_tx: BatchMixMessageSender,
) { ) {
info!("Starting loop cover traffic stream..."); info!("Starting loop cover traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor which HAS TO be called within context of a tokio runtime
let _guard = self.runtime.enter();
LoopCoverTrafficStream::new( LoopCoverTrafficStream::new(
self.key_manager.ack_key(), self.key_manager.ack_key(),
@@ -99,7 +112,7 @@ impl NymClient {
self.as_mix_recipient(), self.as_mix_recipient(),
topology_accessor, topology_accessor,
) )
.start(); .start(self.runtime.handle());
} }
fn start_real_traffic_controller( fn start_real_traffic_controller(
@@ -121,6 +134,10 @@ impl NymClient {
); );
info!("Starting real traffic stream..."); info!("Starting real traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
// When refactoring this restriction should definitely be removed.
let _guard = self.runtime.enter();
RealMessagesController::new( RealMessagesController::new(
controller_config, controller_config,
@@ -130,7 +147,7 @@ impl NymClient {
topology_accessor, topology_accessor,
reply_key_storage, reply_key_storage,
) )
.start(); .start(self.runtime.handle());
} }
// buffer controlling all messages fetched from provider // buffer controlling all messages fetched from provider
@@ -148,10 +165,35 @@ impl NymClient {
mixnet_receiver, mixnet_receiver,
reply_key_storage, reply_key_storage,
) )
.start() .start(self.runtime.handle())
} }
async fn start_gateway_client( #[cfg(feature = "coconut")]
async fn prepare_coconut_credential(&self) -> Credential {
let verification_key = obtain_aggregate_verification_key(
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain aggregate verification key of validators");
let bandwidth_credential = credentials::bandwidth::obtain_signature(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain bandwidth credential");
// the above would presumably be loaded from a file
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
prepare_for_spending(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&bandwidth_credential,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
}
fn start_gateway_client(
&mut self, &mut self,
mixnet_message_sender: MixnetMessageSender, mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender, ack_sender: AcknowledgementSender,
@@ -160,10 +202,6 @@ impl NymClient {
if gateway_id.is_empty() { if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `nym-client` init?") panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
} }
let gateway_owner = self.config.get_base().get_gateway_owner();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_address = self.config.get_base().get_gateway_listener(); let gateway_address = self.config.get_base().get_gateway_listener();
if gateway_address.is_empty() { if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `nym-client` init?") panic!("The address of the gateway is unknown - did you run `nym-client` init?")
@@ -172,45 +210,35 @@ impl NymClient {
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id) let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!"); .expect("provided gateway id is invalid!");
#[cfg(feature = "coconut")] self.runtime.block_on(async {
let bandwidth_controller = BandwidthController::new( #[cfg(feature = "coconut")]
self.config.get_base().get_validator_api_endpoints(), let coconut_credential = self.prepare_coconut_credential().await;
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new( let mut gateway_client = GatewayClient::new(
gateway_address, gateway_address,
self.key_manager.identity_keypair(), self.key_manager.identity_keypair(),
gateway_identity, gateway_identity,
gateway_owner, Some(self.key_manager.gateway_shared_key()),
Some(self.key_manager.gateway_shared_key()), mixnet_message_sender,
mixnet_message_sender, ack_sender,
ack_sender, self.config.get_base().get_gateway_response_timeout(),
self.config.get_base().get_gateway_response_timeout(), );
Some(bandwidth_controller),
);
if self.config.get_base().get_testnet_mode() { gateway_client
gateway_client.set_testnet_mode(true) .authenticate_and_start(
} #[cfg(feature = "coconut")]
gateway_client Some(coconut_credential),
.authenticate_and_start() )
.await .await
.expect("could not authenticate and start up the gateway connection"); .expect("could not authenticate and start up the gateway connection");
gateway_client gateway_client
})
} }
// future responsible for periodically polling directory server and updating // future responsible for periodically polling directory server and updating
// the current global view of topology // the current global view of topology
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) { fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new( let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(), self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(), self.config.get_base().get_topology_refresh_rate(),
@@ -221,10 +249,13 @@ impl NymClient {
// before returning, block entire runtime to refresh the current network view so that any // before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view // components depending on topology would see a non-empty view
info!("Obtaining initial network topology"); info!("Obtaining initial network topology");
topology_refresher.refresh().await; self.runtime.block_on(topology_refresher.refresh());
// TODO: a slightly more graceful termination here // TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await { if !self
.runtime
.block_on(topology_refresher.is_topology_routable())
{
panic!( panic!(
"The current network topology seem to be insufficient to route any packets through\ "The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online" - check if enough nodes and a gateway are online"
@@ -232,7 +263,7 @@ impl NymClient {
} }
info!("Starting topology refresher..."); info!("Starting topology refresher...");
topology_refresher.start(); topology_refresher.start(self.runtime.handle());
} }
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic) // controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -245,7 +276,7 @@ impl NymClient {
gateway_client: GatewayClient, gateway_client: GatewayClient,
) { ) {
info!("Starting mix traffic controller..."); info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client).start(); MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
} }
fn start_websocket_listener( fn start_websocket_listener(
@@ -258,7 +289,8 @@ impl NymClient {
let websocket_handler = let websocket_handler =
websocket::Handler::new(msg_input, buffer_requester, self.as_mix_recipient()); websocket::Handler::new(msg_input, buffer_requester, self.as_mix_recipient());
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler); websocket::Listener::new(self.config.get_listening_port())
.start(self.runtime.handle(), websocket_handler);
} }
/// EXPERIMENTAL DIRECT RUST API /// EXPERIMENTAL DIRECT RUST API
@@ -305,9 +337,9 @@ impl NymClient {
} }
/// blocking version of `start` method. Will run forever (or until SIGINT is sent) /// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) { pub fn run_forever(&mut self) {
self.start().await; self.start();
if let Err(e) = tokio::signal::ctrl_c().await { if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
error!( error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless", "There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e e
@@ -319,7 +351,7 @@ impl NymClient {
); );
} }
pub async fn start(&mut self) { pub fn start(&mut self) {
info!("Starting nym client"); info!("Starting nym client");
// channels for inter-component communication // channels for inter-component communication
// TODO: make the channels be internally created by the relevant components // TODO: make the channels be internally created by the relevant components
@@ -351,17 +383,14 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing, // the components are started in very specific order. Unless you know what you are doing,
// do not change that. // do not change that.
self.start_topology_refresher(shared_topology_accessor.clone()) self.start_topology_refresher(shared_topology_accessor.clone());
.await;
self.start_received_messages_buffer_controller( self.start_received_messages_buffer_controller(
received_buffer_request_receiver, received_buffer_request_receiver,
mixnet_messages_receiver, mixnet_messages_receiver,
reply_key_storage.clone(), reply_key_storage.clone(),
); );
let gateway_client = self let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client); self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
self.start_real_traffic_controller( self.start_real_traffic_controller(
+27 -92
View File
@@ -1,23 +1,15 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::commands::override_config;
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
use client_core::client::key_manager::KeyManager; use client_core::client::key_manager::KeyManager;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
#[cfg(feature = "coconut")]
use coconut_interface::{hash_to_scalar, Credential, Parameters};
use config::NymConfig; use config::NymConfig;
#[cfg(feature = "coconut")]
use credentials::coconut::bandwidth::{
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
};
#[cfg(feature = "coconut")]
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::{encryption, identity}; use crypto::asymmetric::{encryption, identity};
use gateway_client::GatewayClient; use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys; use gateway_requests::registration::handshake::SharedKeys;
#[cfg(feature = "coconut")]
use network_defaults::BANDWIDTH_VALUE;
use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity; use nymsphinx::addressing::nodes::NodeIdentity;
use rand::rngs::OsRng; use rand::rngs::OsRng;
@@ -29,17 +21,8 @@ use std::time::Duration;
use topology::{filter::VersionFilterable, gateway}; use topology::{filter::VersionFilterable, gateway};
use url::Url; use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
TESTNET_MODE_ARG_NAME,
};
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init") App::new("init")
.about("Initialise a Nym client. Do this first!") .about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id") .arg(Arg::with_name("id")
.long("id") .long("id")
@@ -53,9 +36,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.takes_value(true) .takes_value(true)
) )
.arg(Arg::with_name("validators") .arg(Arg::with_name("validators")
.long("validators") .long("validators")
.help("Comma separated list of rest endpoints of the validators") .help("Comma separated list of rest endpoints of the validators")
.takes_value(true), .takes_value(true),
) )
.arg(Arg::with_name("disable-socket") .arg(Arg::with_name("disable-socket")
.long("disable-socket") .long("disable-socket")
@@ -71,61 +54,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("fastmode") .long("fastmode")
.hidden(true) // this will prevent this flag from being displayed in `--help` .hidden(true) // this will prevent this flag from being displayed in `--help`
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init") .help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
) )
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
.required(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
.required(true)
);
app
}
// this behaviour should definitely be changed, we shouldn't
// need to get bandwidth credential for registration
#[cfg(feature = "coconut")]
async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
let verification_key = obtain_aggregate_verification_key(validators)
.await
.expect("could not obtain aggregate verification key of validators");
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
serial_number: params.random_scalar(),
binding_number: params.random_scalar(),
voucher_value: hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
voucher_info: hash_to_scalar(String::from("BandwidthVoucher").as_bytes()),
};
let bandwidth_credential =
obtain_signature(&params, &bandwidth_credential_attributes, validators)
.await
.expect("could not obtain bandwidth credential");
prepare_for_spending(
raw_identity,
&bandwidth_credential,
&bandwidth_credential_attributes,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
} }
async fn register_with_gateway( async fn register_with_gateway(
@@ -136,7 +65,6 @@ async fn register_with_gateway(
let mut gateway_client = GatewayClient::new_init( let mut gateway_client = GatewayClient::new_init(
gateway.clients_address(), gateway.clients_address(),
gateway.identity_key, gateway.identity_key,
gateway.owner.clone(),
our_identity.clone(), our_identity.clone(),
timeout, timeout,
); );
@@ -220,7 +148,7 @@ fn show_address(config: &Config) {
println!("\nThe address of this client is: {}", client_recipient); println!("\nThe address of this client is: {}", client_recipient);
} }
pub async fn execute(matches: ArgMatches<'static>) { pub fn execute(matches: &ArgMatches) {
println!("Initialising client..."); println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now let id = matches.value_of("id").unwrap(); // required for now
@@ -238,7 +166,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
// TODO: ideally that should be the last thing that's being done to config. // TODO: ideally that should be the last thing that's being done to config.
// However, we are later further overriding it with gateway id // However, we are later further overriding it with gateway id
config = override_config(config, &matches); config = override_config(config, matches);
if matches.is_present("fastmode") { if matches.is_present("fastmode") {
config.get_base_mut().set_high_default_traffic_volume(); config.get_base_mut().set_high_default_traffic_volume();
} }
@@ -251,19 +179,26 @@ pub async fn execute(matches: ArgMatches<'static>) {
let chosen_gateway_id = matches.value_of("gateway"); let chosen_gateway_id = matches.value_of("gateway");
let gateway_details = gateway_details( let registration_fut = async {
config.get_base().get_validator_api_endpoints(), let gate_details = gateway_details(
chosen_gateway_id, config.get_base().get_validator_api_endpoints(),
) chosen_gateway_id,
.await; )
let shared_keys = .await;
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await; config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.clients_address())
};
config.get_base_mut().with_gateway_endpoint( // TODO: is there perhaps a way to make it work without having to spawn entire runtime?
gateway_details.identity_key.to_base58_string(), let rt = tokio::runtime::Runtime::new().unwrap();
gateway_details.owner.clone(), let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
gateway_details.clients_address(), config
); .get_base_mut()
.with_gateway_listener(gateway_listener);
key_manager.insert_gateway_shared_key(shared_keys); key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
+1 -34
View File
@@ -5,18 +5,6 @@ use crate::client::config::{Config, SocketType};
use clap::ArgMatches; use clap::ArgMatches;
use url::Url; use url::Url;
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
pub(crate) mod init; pub(crate) mod init;
pub(crate) mod run; pub(crate) mod run;
pub(crate) mod upgrade; pub(crate) mod upgrade;
@@ -32,7 +20,7 @@ fn parse_validators(raw: &str) -> Vec<Url> {
.collect() .collect()
} }
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config { pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
if let Some(raw_validators) = matches.value_of("validators") { if let Some(raw_validators) = matches.value_of("validators") {
config config
.get_base_mut() .get_base_mut()
@@ -55,26 +43,5 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> C
config = config.with_port(port.unwrap()); config = config.with_port(port.unwrap());
} }
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
} else if !cfg!(feature = "eth") {
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
config.get_base_mut().with_eth_private_key(eth_private_key);
} else if !cfg!(feature = "eth") {
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
}
if !cfg!(feature = "eth") || matches.is_present(TESTNET_MODE_ARG_NAME) {
config.get_base_mut().with_testnet_mode(true)
}
config config
} }
+4 -26
View File
@@ -4,16 +4,13 @@
use crate::client::config::Config; use crate::client::config::Config;
use crate::client::NymClient; use crate::client::NymClient;
use crate::commands::override_config; use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
use config::NymConfig; use config::NymConfig;
use log::*; use log::*;
use version_checker::is_minor_version_compatible; use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("run") App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters") .about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id") .arg(Arg::with_name("id")
.long("id") .long("id")
@@ -41,26 +38,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("port") .long("port")
.help("Port for the socket (if applicable) to listen on") .help("Port for the socket (if applicable) to listen on")
.takes_value(true) .takes_value(true)
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
) )
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true));
app
} }
// this only checks compatibility between config the binary. It does not take into consideration // this only checks compatibility between config the binary. It does not take into consideration
@@ -82,7 +60,7 @@ fn version_check(cfg: &Config) -> bool {
} }
} }
pub async fn execute(matches: ArgMatches<'static>) { pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap(); let id = matches.value_of("id").unwrap();
let mut config = match Config::load_from_file(Some(id)) { let mut config = match Config::load_from_file(Some(id)) {
@@ -93,12 +71,12 @@ pub async fn execute(matches: ArgMatches<'static>) {
} }
}; };
config = override_config(config, &matches); config = override_config(config, matches);
if !version_check(&config) { if !version_check(&config) {
error!("failed the local version check"); error!("failed the local version check");
return; return;
} }
NymClient::new(config).run_forever().await; NymClient::new(config).run_forever();
} }
+3 -3
View File
@@ -95,7 +95,7 @@ fn parse_package_version() -> Version {
fn minor_0_12_upgrade( fn minor_0_12_upgrade(
mut config: Config, mut config: Config,
_matches: &ArgMatches<'_>, _matches: &ArgMatches,
config_version: &Version, config_version: &Version,
package_version: &Version, package_version: &Version,
) -> Config { ) -> Config {
@@ -131,7 +131,7 @@ fn minor_0_12_upgrade(
config config
} }
fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: Version) { fn do_upgrade(mut config: Config, matches: &ArgMatches, package_version: Version) {
loop { loop {
let config_version = parse_config_version(&config); let config_version = parse_config_version(&config);
@@ -151,7 +151,7 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: Ver
} }
} }
pub fn execute(matches: &ArgMatches<'_>) { pub fn execute(matches: &ArgMatches) {
let package_version = parse_package_version(); let package_version = parse_package_version();
let id = matches.value_of("id").unwrap(); let id = matches.value_of("id").unwrap();
+8 -41
View File
@@ -1,21 +1,19 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, App, ArgMatches}; use clap::{App, ArgMatches};
pub mod client; pub mod client;
pub mod commands; pub mod commands;
pub mod websocket; pub mod websocket;
#[tokio::main] fn main() {
async fn main() {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
setup_logging(); setup_logging();
println!("{}", banner()); println!("{}", banner());
let arg_matches = App::new("Nym Client") let arg_matches = App::new("Nym Client")
.version(crate_version!()) .version(env!("CARGO_PKG_VERSION"))
.long_version(&*long_version())
.author("Nymtech") .author("Nymtech")
.about("Implementation of the Nym Client") .about("Implementation of the Nym Client")
.subcommand(commands::init::command_args()) .subcommand(commands::init::command_args())
@@ -23,13 +21,13 @@ async fn main() {
.subcommand(commands::upgrade::command_args()) .subcommand(commands::upgrade::command_args())
.get_matches(); .get_matches();
execute(arg_matches).await; execute(arg_matches);
} }
async fn execute(matches: ArgMatches<'static>) { fn execute(matches: ArgMatches) {
match matches.subcommand() { match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m.clone()).await, ("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m.clone()).await, ("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m), ("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()), _ => println!("{}", usage()),
} }
@@ -52,38 +50,7 @@ fn banner() -> String {
(client - version {:}) (client - version {:})
"#, "#,
crate_version!() env!("CARGO_PKG_VERSION")
)
}
fn long_version() -> String {
format!(
r#"
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
"Build Version:",
env!("VERGEN_BUILD_SEMVER"),
"Commit SHA:",
env!("VERGEN_GIT_SHA"),
"Commit Date:",
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
"Commit Branch:",
env!("VERGEN_GIT_BRANCH"),
"rustc Version:",
env!("VERGEN_RUSTC_SEMVER"),
"rustc Channel:",
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
) )
} }
+3 -2
View File
@@ -5,6 +5,7 @@ use super::handler::Handler;
use log::*; use log::*;
use std::{net::SocketAddr, process, sync::Arc}; use std::{net::SocketAddr, process, sync::Arc};
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio::runtime;
use tokio::{sync::Notify, task::JoinHandle}; use tokio::{sync::Notify, task::JoinHandle};
enum State { enum State {
@@ -86,9 +87,9 @@ impl Listener {
} }
} }
pub(crate) fn start(mut self, handler: Handler) -> JoinHandle<()> { pub(crate) fn start(mut self, rt_handle: &runtime::Handle, handler: Handler) -> JoinHandle<()> {
info!("Running websocket on {:?}", self.address.to_string()); info!("Running websocket on {:?}", self.address.to_string());
tokio::spawn(async move { self.run(handler).await }) rt_handle.spawn(async move { self.run(handler).await })
} }
} }
+3 -3
View File
@@ -1,8 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
pub(crate) use handler::Handler;
pub(crate) use listener::Listener;
pub(crate) mod handler; pub(crate) mod handler;
pub(crate) mod listener; pub(crate) mod listener;
pub(crate) use handler::Handler;
pub(crate) use listener::Listener;
+1 -1
View File
@@ -2,7 +2,7 @@
name = "websocket-requests" name = "websocket-requests"
version = "0.1.0" version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"] authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+3 -9
View File
@@ -1,9 +1,8 @@
[package] [package]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "0.12.1" version = "0.11.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021" edition = "2018"
rust-version = "1.56"
[lib] [lib]
name = "nym_socks5" name = "nym_socks5"
@@ -32,18 +31,13 @@ crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" } gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" } gateway-requests = { path = "../../gateway/gateway-requests" }
nymsphinx = { path = "../../common/nymsphinx" } nymsphinx = { path = "../../common/nymsphinx" }
ordered-buffer = { path = "../../common/socks5/ordered-buffer" } ordered-buffer = {path = "../../common/socks5/ordered-buffer"}
socks5-requests = { path = "../../common/socks5/requests" } socks5-requests = { path = "../../common/socks5/requests" }
topology = { path = "../../common/topology" } topology = { path = "../../common/topology" }
pemstore = { path = "../../common/pemstore" } pemstore = { path = "../../common/pemstore" }
proxy-helpers = { path = "../../common/socks5/proxy-helpers" } proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
validator-client = { path = "../../common/client-libs/validator-client" } validator-client = { path = "../../common/client-libs/validator-client" }
version-checker = { path = "../../common/version-checker" } version-checker = { path = "../../common/version-checker" }
network-defaults = { path = "../../common/network-defaults" }
[features] [features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"] coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
eth = []
[build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
-8
View File
@@ -1,8 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
fn main() {
vergen(Config::default()).expect("failed to extract build metadata")
}
+8 -27
View File
@@ -5,7 +5,7 @@ pub(crate) fn config_template() -> &'static str {
// While using normal toml marshalling would have been way simpler with less overhead, // While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of // I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields. // particular fields.
// Note: any changes to the template must be reflected in the appropriate structs. // Note: any changes to the template must be reflected in the appropriate structs in verloc.
r#" r#"
# This is a TOML config file. # This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml # For more information, see https://github.com/toml-lang/toml
@@ -19,10 +19,6 @@ version = '{{ client.version }}'
# Human readable ID of this particular client. # Human readable ID of this particular client.
id = '{{ client.id }}' id = '{{ client.id }}'
# Indicates whether this client is running in a testnet mode, thus attempting
# to claim bandwidth without presenting bandwidth credentials.
testnet_mode = {{ client.testnet_mode }}
# Addresses to APIs running on validator from which the client gets the view of the network. # Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [ validator_api_urls = [
{{#each client.validator_api_urls }} {{#each client.validator_api_urls }}
@@ -46,19 +42,14 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
# sent but not received back. # sent but not received back.
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}' reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to directory containing public/private keys used for bandwidth token purchase.
# Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
# The public key is the name of the file, while the private key is the content.
backup_bandwidth_token_keys_dir = '{{ client.backup_bandwidth_token_keys_dir }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options ##### ##### additional client config options #####
# ID of the gateway from which the client should be fetching messages.
gateway_id = '{{ client.gateway_id }}'
# Address of the gateway listener to which all client requests should be sent.
gateway_listener = '{{ client.gateway_listener }}'
# A gateway specific, optional, base58 stringified shared key used for # A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway. # communication with particular gateway.
gateway_shared_key_file = '{{ client.gateway_shared_key_file }}' gateway_shared_key_file = '{{ client.gateway_shared_key_file }}'
@@ -66,22 +57,12 @@ gateway_shared_key_file = '{{ client.gateway_shared_key_file }}'
# Path to file containing key used for encrypting and decrypting the content of an # Path to file containing key used for encrypting and decrypting the content of an
# acknowledgement so that nobody besides the client knows which packet it refers to. # acknowledgement so that nobody besides the client knows which packet it refers to.
ack_key_file = '{{ client.ack_key_file }}' ack_key_file = '{{ client.ack_key_file }}'
##### advanced configuration options ##### ##### advanced configuration options #####
# Absolute path to the home Nym Clients directory. # Absolute path to the home Nym Clients directory.
nym_root_directory = '{{ client.nym_root_directory }}' nym_root_directory = '{{ client.nym_root_directory }}'
[client.gateway_endpoint]
# ID of the gateway from which the client should be fetching messages.
gateway_id = '{{ client.gateway_endpoint.gateway_id }}'
# Address of the gateway owner to which the client should send messages.
gateway_owner = '{{ client.gateway_endpoint.gateway_owner }}'
# Address of the gateway listener to which all client requests should be sent.
gateway_listener = '{{ client.gateway_endpoint.gateway_listener }}'
##### socket config options ##### ##### socket config options #####
+89 -60
View File
@@ -1,6 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream; use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{ use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender, InputMessage, InputMessageReceiver, InputMessageSender,
@@ -20,7 +25,6 @@ use client_core::client::topology_control::{
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use crypto::asymmetric::identity; use crypto::asymmetric::identity;
use futures::channel::mpsc; use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{ use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver, AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender, MixnetMessageSender,
@@ -28,12 +32,12 @@ use gateway_client::{
use log::*; use log::*;
use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity; use nymsphinx::addressing::nodes::NodeIdentity;
use tokio::runtime::Runtime;
use crate::client::config::Config; #[cfg(feature = "coconut")]
use crate::socks::{ use coconut_interface::Credential;
authentication::{AuthenticationMethods, Authenticator, User}, #[cfg(feature = "coconut")]
server::SphinxSocksServer, use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
};
pub(crate) mod config; pub(crate) mod config;
@@ -42,6 +46,11 @@ pub struct NymClient {
/// key filepaths, etc. /// key filepaths, etc.
config: Config, config: Config,
/// Tokio runtime used for futures execution.
// TODO: JS: Personally I think I prefer the implicit way of using it that we've done with the
// gateway.
runtime: Runtime,
/// KeyManager object containing smart pointers to all relevant keys used by the client. /// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager, key_manager: KeyManager,
} }
@@ -52,6 +61,7 @@ impl NymClient {
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys"); let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
NymClient { NymClient {
runtime: Runtime::new().unwrap(),
config, config,
key_manager, key_manager,
} }
@@ -75,6 +85,9 @@ impl NymClient {
mix_tx: BatchMixMessageSender, mix_tx: BatchMixMessageSender,
) { ) {
info!("Starting loop cover traffic stream..."); info!("Starting loop cover traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor which HAS TO be called within context of a tokio runtime
let _guard = self.runtime.enter();
LoopCoverTrafficStream::new( LoopCoverTrafficStream::new(
self.key_manager.ack_key(), self.key_manager.ack_key(),
@@ -87,7 +100,7 @@ impl NymClient {
self.as_mix_recipient(), self.as_mix_recipient(),
topology_accessor, topology_accessor,
) )
.start(); .start(self.runtime.handle());
} }
fn start_real_traffic_controller( fn start_real_traffic_controller(
@@ -109,6 +122,10 @@ impl NymClient {
); );
info!("Starting real traffic stream..."); info!("Starting real traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
// When refactoring this restriction should definitely be removed.
let _guard = self.runtime.enter();
RealMessagesController::new( RealMessagesController::new(
controller_config, controller_config,
@@ -118,7 +135,7 @@ impl NymClient {
topology_accessor, topology_accessor,
reply_key_storage, reply_key_storage,
) )
.start(); .start(self.runtime.handle());
} }
// buffer controlling all messages fetched from provider // buffer controlling all messages fetched from provider
@@ -136,10 +153,35 @@ impl NymClient {
mixnet_receiver, mixnet_receiver,
reply_key_storage, reply_key_storage,
) )
.start() .start(self.runtime.handle())
} }
async fn start_gateway_client( #[cfg(feature = "coconut")]
async fn prepare_coconut_credential(&self) -> Credential {
let verification_key = obtain_aggregate_verification_key(
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain aggregate verification key of validators");
let bandwidth_credential = credentials::bandwidth::obtain_signature(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain bandwidth credential");
// the above would presumably be loaded from a file
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
prepare_for_spending(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&bandwidth_credential,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
}
fn start_gateway_client(
&mut self, &mut self,
mixnet_message_sender: MixnetMessageSender, mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender, ack_sender: AcknowledgementSender,
@@ -148,10 +190,6 @@ impl NymClient {
if gateway_id.is_empty() { if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `nym-client` init?") panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
} }
let gateway_owner = self.config.get_base().get_gateway_owner();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_address = self.config.get_base().get_gateway_listener(); let gateway_address = self.config.get_base().get_gateway_listener();
if gateway_address.is_empty() { if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `nym-client` init?") panic!("The address of the gateway is unknown - did you run `nym-client` init?")
@@ -160,45 +198,35 @@ impl NymClient {
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id) let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!"); .expect("provided gateway id is invalid!");
#[cfg(feature = "coconut")] self.runtime.block_on(async {
let bandwidth_controller = BandwidthController::new( #[cfg(feature = "coconut")]
self.config.get_base().get_validator_api_endpoints(), let coconut_credential = self.prepare_coconut_credential().await;
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new( let mut gateway_client = GatewayClient::new(
gateway_address, gateway_address,
self.key_manager.identity_keypair(), self.key_manager.identity_keypair(),
gateway_identity, gateway_identity,
gateway_owner, Some(self.key_manager.gateway_shared_key()),
Some(self.key_manager.gateway_shared_key()), mixnet_message_sender,
mixnet_message_sender, ack_sender,
ack_sender, self.config.get_base().get_gateway_response_timeout(),
self.config.get_base().get_gateway_response_timeout(), );
Some(bandwidth_controller),
);
if self.config.get_base().get_testnet_mode() { gateway_client
gateway_client.set_testnet_mode(true) .authenticate_and_start(
} #[cfg(feature = "coconut")]
gateway_client Some(coconut_credential),
.authenticate_and_start() )
.await .await
.expect("could not authenticate and start up the gateway connection"); .expect("could not authenticate and start up the gateway connection");
gateway_client gateway_client
})
} }
// future responsible for periodically polling directory server and updating // future responsible for periodically polling directory server and updating
// the current global view of topology // the current global view of topology
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) { fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new( let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(), self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(), self.config.get_base().get_topology_refresh_rate(),
@@ -209,10 +237,13 @@ impl NymClient {
// before returning, block entire runtime to refresh the current network view so that any // before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view // components depending on topology would see a non-empty view
info!("Obtaining initial network topology"); info!("Obtaining initial network topology");
topology_refresher.refresh().await; self.runtime.block_on(topology_refresher.refresh());
// TODO: a slightly more graceful termination here // TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await { if !self
.runtime
.block_on(topology_refresher.is_topology_routable())
{
panic!( panic!(
"The current network topology seem to be insufficient to route any packets through\ "The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online" - check if enough nodes and a gateway are online"
@@ -220,7 +251,7 @@ impl NymClient {
} }
info!("Starting topology refresher..."); info!("Starting topology refresher...");
topology_refresher.start(); topology_refresher.start(self.runtime.handle());
} }
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic) // controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -233,7 +264,7 @@ impl NymClient {
gateway_client: GatewayClient, gateway_client: GatewayClient,
) { ) {
info!("Starting mix traffic controller..."); info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client).start(); MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
} }
fn start_socks5_listener( fn start_socks5_listener(
@@ -252,13 +283,14 @@ impl NymClient {
self.config.get_provider_mix_address(), self.config.get_provider_mix_address(),
self.as_mix_recipient(), self.as_mix_recipient(),
); );
tokio::spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await }); self.runtime
.spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
} }
/// blocking version of `start` method. Will run forever (or until SIGINT is sent) /// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) { pub fn run_forever(&mut self) {
self.start().await; self.start();
if let Err(e) = tokio::signal::ctrl_c().await { if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
error!( error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless", "There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e e
@@ -270,7 +302,7 @@ impl NymClient {
); );
} }
pub async fn start(&mut self) { pub fn start(&mut self) {
info!("Starting nym client"); info!("Starting nym client");
// channels for inter-component communication // channels for inter-component communication
// TODO: make the channels be internally created by the relevant components // TODO: make the channels be internally created by the relevant components
@@ -302,17 +334,14 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing, // the components are started in very specific order. Unless you know what you are doing,
// do not change that. // do not change that.
self.start_topology_refresher(shared_topology_accessor.clone()) self.start_topology_refresher(shared_topology_accessor.clone());
.await;
self.start_received_messages_buffer_controller( self.start_received_messages_buffer_controller(
received_buffer_request_receiver, received_buffer_request_receiver,
mixnet_messages_receiver, mixnet_messages_receiver,
reply_key_storage.clone(), reply_key_storage.clone(),
); );
let gateway_client = self let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client); self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
self.start_real_traffic_controller( self.start_real_traffic_controller(
+27 -92
View File
@@ -1,23 +1,15 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::commands::override_config;
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
use client_core::client::key_manager::KeyManager; use client_core::client::key_manager::KeyManager;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
#[cfg(feature = "coconut")]
use coconut_interface::{hash_to_scalar, Credential, Parameters};
use config::NymConfig; use config::NymConfig;
#[cfg(feature = "coconut")]
use credentials::coconut::bandwidth::{
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
};
#[cfg(feature = "coconut")]
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::{encryption, identity}; use crypto::asymmetric::{encryption, identity};
use gateway_client::GatewayClient; use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys; use gateway_requests::registration::handshake::SharedKeys;
#[cfg(feature = "coconut")]
use network_defaults::BANDWIDTH_VALUE;
use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity; use nymsphinx::addressing::nodes::NodeIdentity;
use rand::{prelude::SliceRandom, rngs::OsRng, thread_rng}; use rand::{prelude::SliceRandom, rngs::OsRng, thread_rng};
@@ -27,17 +19,8 @@ use std::time::Duration;
use topology::{filter::VersionFilterable, gateway}; use topology::{filter::VersionFilterable, gateway};
use url::Url; use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
TESTNET_MODE_ARG_NAME,
};
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init") App::new("init")
.about("Initialise a Nym client. Do this first!") .about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id") .arg(Arg::with_name("id")
.long("id") .long("id")
@@ -57,9 +40,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.takes_value(true) .takes_value(true)
) )
.arg(Arg::with_name("validators") .arg(Arg::with_name("validators")
.long("validators") .long("validators")
.help("Comma separated list of rest endpoints of the validators") .help("Comma separated list of rest endpoints of the validators")
.takes_value(true), .takes_value(true),
) )
.arg(Arg::with_name("port") .arg(Arg::with_name("port")
.short("p") .short("p")
@@ -71,61 +54,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("fastmode") .long("fastmode")
.hidden(true) // this will prevent this flag from being displayed in `--help` .hidden(true) // this will prevent this flag from being displayed in `--help`
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init") .help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
) )
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
.required(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
.required(true)
);
app
}
// this behaviour should definitely be changed, we shouldn't
// need to get bandwidth credential for registration
#[cfg(feature = "coconut")]
async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
let verification_key = obtain_aggregate_verification_key(validators)
.await
.expect("could not obtain aggregate verification key of validators");
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
serial_number: params.random_scalar(),
binding_number: params.random_scalar(),
voucher_value: hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
voucher_info: hash_to_scalar("BandwidthVoucher"),
};
let bandwidth_credential =
obtain_signature(&params, &bandwidth_credential_attributes, validators)
.await
.expect("could not obtain bandwidth credential");
prepare_for_spending(
raw_identity,
&bandwidth_credential,
&bandwidth_credential_attributes,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
} }
async fn register_with_gateway( async fn register_with_gateway(
@@ -136,7 +65,6 @@ async fn register_with_gateway(
let mut gateway_client = GatewayClient::new_init( let mut gateway_client = GatewayClient::new_init(
gateway.clients_address(), gateway.clients_address(),
gateway.identity_key, gateway.identity_key,
gateway.owner.clone(),
our_identity.clone(), our_identity.clone(),
timeout, timeout,
); );
@@ -220,7 +148,7 @@ fn show_address(config: &Config) {
println!("\nThe address of this client is: {}", client_recipient); println!("\nThe address of this client is: {}", client_recipient);
} }
pub async fn execute(matches: ArgMatches<'static>) { pub fn execute(matches: &ArgMatches) {
println!("Initialising client..."); println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now let id = matches.value_of("id").unwrap(); // required for now
@@ -239,7 +167,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
// TODO: ideally that should be the last thing that's being done to config. // TODO: ideally that should be the last thing that's being done to config.
// However, we are later further overriding it with gateway id // However, we are later further overriding it with gateway id
config = override_config(config, &matches); config = override_config(config, matches);
if matches.is_present("fastmode") { if matches.is_present("fastmode") {
config.get_base_mut().set_high_default_traffic_volume(); config.get_base_mut().set_high_default_traffic_volume();
} }
@@ -252,19 +180,26 @@ pub async fn execute(matches: ArgMatches<'static>) {
let chosen_gateway_id = matches.value_of("gateway"); let chosen_gateway_id = matches.value_of("gateway");
let gateway_details = gateway_details( let registration_fut = async {
config.get_base().get_validator_api_endpoints(), let gate_details = gateway_details(
chosen_gateway_id, config.get_base().get_validator_api_endpoints(),
) chosen_gateway_id,
.await; )
let shared_keys = .await;
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await; config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.clients_address())
};
config.get_base_mut().with_gateway_endpoint( // TODO: is there perhaps a way to make it work without having to spawn entire runtime?
gateway_details.identity_key.to_base58_string(), let rt = tokio::runtime::Runtime::new().unwrap();
gateway_details.owner.clone(), let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
gateway_details.clients_address(), config
); .get_base_mut()
.with_gateway_listener(gateway_listener);
key_manager.insert_gateway_shared_key(shared_keys); key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
+1 -34
View File
@@ -9,18 +9,6 @@ pub(crate) mod init;
pub(crate) mod run; pub(crate) mod run;
pub(crate) mod upgrade; pub(crate) mod upgrade;
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
fn parse_validators(raw: &str) -> Vec<Url> { fn parse_validators(raw: &str) -> Vec<Url> {
raw.split(',') raw.split(',')
.map(|raw_validator| { .map(|raw_validator| {
@@ -32,7 +20,7 @@ fn parse_validators(raw: &str) -> Vec<Url> {
.collect() .collect()
} }
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config { pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
if let Some(raw_validators) = matches.value_of("validators") { if let Some(raw_validators) = matches.value_of("validators") {
config config
.get_base_mut() .get_base_mut()
@@ -51,26 +39,5 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> C
config = config.with_port(port.unwrap()); config = config.with_port(port.unwrap());
} }
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
} else if !cfg!(feature = "eth") {
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
config.get_base_mut().with_eth_private_key(eth_private_key);
} else if !cfg!(feature = "eth") {
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
}
if !cfg!(feature = "eth") || matches.is_present(TESTNET_MODE_ARG_NAME) {
config.get_base_mut().with_testnet_mode(true)
}
config config
} }
+4 -26
View File
@@ -4,16 +4,13 @@
use crate::client::config::Config; use crate::client::config::Config;
use crate::client::NymClient; use crate::client::NymClient;
use crate::commands::override_config; use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
use config::NymConfig; use config::NymConfig;
use log::*; use log::*;
use version_checker::is_minor_version_compatible; use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("run") App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters") .about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id") .arg(Arg::with_name("id")
.long("id") .long("id")
@@ -47,26 +44,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("port") .long("port")
.help("Port for the socket to listen on") .help("Port for the socket to listen on")
.takes_value(true) .takes_value(true)
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
) )
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true));
app
} }
// this only checks compatibility between config the binary. It does not take into consideration // this only checks compatibility between config the binary. It does not take into consideration
@@ -88,7 +66,7 @@ fn version_check(cfg: &Config) -> bool {
} }
} }
pub async fn execute(matches: ArgMatches<'static>) { pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap(); let id = matches.value_of("id").unwrap();
let mut config = match Config::load_from_file(Some(id)) { let mut config = match Config::load_from_file(Some(id)) {
@@ -99,12 +77,12 @@ pub async fn execute(matches: ArgMatches<'static>) {
} }
}; };
config = override_config(config, &matches); config = override_config(config, matches);
if !version_check(&config) { if !version_check(&config) {
error!("failed the local version check"); error!("failed the local version check");
return; return;
} }
NymClient::new(config).run_forever().await; NymClient::new(config).run_forever();
} }
+3 -3
View File
@@ -95,7 +95,7 @@ fn parse_package_version() -> Version {
fn minor_0_12_upgrade( fn minor_0_12_upgrade(
mut config: Config, mut config: Config,
_matches: &ArgMatches<'_>, _matches: &ArgMatches,
config_version: &Version, config_version: &Version,
package_version: &Version, package_version: &Version,
) -> Config { ) -> Config {
@@ -131,7 +131,7 @@ fn minor_0_12_upgrade(
config config
} }
fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: Version) { fn do_upgrade(mut config: Config, matches: &ArgMatches, package_version: Version) {
loop { loop {
let config_version = parse_config_version(&config); let config_version = parse_config_version(&config);
@@ -151,7 +151,7 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: Ver
} }
} }
pub fn execute(matches: &ArgMatches<'_>) { pub fn execute(matches: &ArgMatches) {
let package_version = parse_package_version(); let package_version = parse_package_version();
let id = matches.value_of("id").unwrap(); let id = matches.value_of("id").unwrap();
+7 -40
View File
@@ -1,14 +1,13 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, App, ArgMatches}; use clap::{App, ArgMatches};
pub mod client; pub mod client;
mod commands; mod commands;
pub mod socks; pub mod socks;
#[tokio::main] fn main() {
async fn main() {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
setup_logging(); setup_logging();
println!("{}", banner()); println!("{}", banner());
@@ -16,20 +15,19 @@ async fn main() {
let arg_matches = App::new("Nym Socks5 Proxy") let arg_matches = App::new("Nym Socks5 Proxy")
.version(env!("CARGO_PKG_VERSION")) .version(env!("CARGO_PKG_VERSION"))
.author("Nymtech") .author("Nymtech")
.long_version(&*long_version())
.about("A Socks5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address") .about("A Socks5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address")
.subcommand(commands::init::command_args()) .subcommand(commands::init::command_args())
.subcommand(commands::run::command_args()) .subcommand(commands::run::command_args())
.subcommand(commands::upgrade::command_args()) .subcommand(commands::upgrade::command_args())
.get_matches(); .get_matches();
execute(arg_matches).await; execute(arg_matches);
} }
async fn execute(matches: ArgMatches<'static>) { fn execute(matches: ArgMatches) {
match matches.subcommand() { match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m.clone()).await, ("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m.clone()).await, ("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m), ("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()), _ => println!("{}", usage()),
} }
@@ -52,38 +50,7 @@ fn banner() -> String {
(socks5 proxy - version {:}) (socks5 proxy - version {:})
"#, "#,
crate_version!() env!("CARGO_PKG_VERSION")
)
}
fn long_version() -> String {
format!(
r#"
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
"Build Version:",
env!("VERGEN_BUILD_SEMVER"),
"Commit SHA:",
env!("VERGEN_GIT_SHA"),
"Commit Date:",
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
"Commit Branch:",
env!("VERGEN_GIT_BRANCH"),
"rustc Version:",
env!("VERGEN_RUSTC_SEMVER"),
"rustc Channel:",
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
) )
} }
+7 -17
View File
@@ -11,7 +11,6 @@
"@tauri-apps/api": "^1.0.0-beta.4", "@tauri-apps/api": "^1.0.0-beta.4",
"compression": "^1.7.1", "compression": "^1.7.1",
"polka": "next", "polka": "next",
"qrious": "^4.0.2",
"sirv": "^1.0.0" "sirv": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
@@ -6759,11 +6758,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/qrious": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/qrious/-/qrious-4.0.2.tgz",
"integrity": "sha512-xWPJIrK1zu5Ypn898fBp8RHkT/9ibquV2Kv24S/JY9VYEhMBMKur1gHVsOiNUh7PHP9uCgejjpZUHUIXXKoU/g=="
},
"node_modules/query-string": { "node_modules/query-string": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
@@ -7585,10 +7579,11 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/simple-get": { "node_modules/simple-get": {
"version": "3.1.1", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"decompress-response": "^4.2.0", "decompress-response": "^4.2.0",
"once": "^1.3.1", "once": "^1.3.1",
@@ -13479,11 +13474,6 @@
"escape-goat": "^2.0.0" "escape-goat": "^2.0.0"
} }
}, },
"qrious": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/qrious/-/qrious-4.0.2.tgz",
"integrity": "sha512-xWPJIrK1zu5Ypn898fBp8RHkT/9ibquV2Kv24S/JY9VYEhMBMKur1gHVsOiNUh7PHP9uCgejjpZUHUIXXKoU/g=="
},
"query-string": { "query-string": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
@@ -14046,9 +14036,9 @@
"dev": true "dev": true
}, },
"simple-get": { "simple-get": {
"version": "3.1.1", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
"dev": true, "dev": true,
"requires": { "requires": {
"decompress-response": "^4.2.0", "decompress-response": "^4.2.0",
+1 -1
View File
@@ -6,7 +6,7 @@ authors = ["you"]
license = "" license = ""
repository = "" repository = ""
default-run = "app" default-run = "app"
edition = "2021" edition = "2018"
build = "src/build.rs" build = "src/build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+17 -29
View File
@@ -3,24 +3,21 @@
windows_subsystem = "windows" windows_subsystem = "windows"
)] )]
use std::sync::Arc;
use tokio::sync::RwLock;
use url::Url;
use coconut_interface::{ use coconut_interface::{
self, hash_to_scalar, Attribute, Credential, Parameters, Signature, Theta, VerificationKey, self, hash_to_scalar, Attribute, Credential, Parameters, Signature, Theta, VerificationKey,
}; };
use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key}; use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key};
use std::sync::Arc;
use tokio::sync::RwLock;
use url::Url;
struct State { struct State {
signatures: Vec<Signature>, signatures: Vec<Signature>,
n_attributes: u32, n_attributes: u32,
params: Parameters, params: Parameters,
serial_number: Attribute, public_attributes_bytes: Vec<Vec<u8>>,
binding_number: Attribute, public_attributes: Vec<Attribute>,
voucher_value: Attribute, private_attributes: Vec<Attribute>,
voucher_info: Attribute,
aggregated_verification_key: Option<VerificationKey>, aggregated_verification_key: Option<VerificationKey>,
} }
@@ -40,10 +37,9 @@ impl State {
signatures: Vec::new(), signatures: Vec::new(),
n_attributes, n_attributes,
params, params,
serial_number: private_attributes[0], public_attributes_bytes,
binding_number: private_attributes[1], public_attributes,
voucher_value: public_attributes[0], private_attributes,
voucher_info: public_attributes[1],
aggregated_verification_key: None, aggregated_verification_key: None,
} }
} }
@@ -67,8 +63,8 @@ async fn randomise_credential(
) -> Result<Vec<Signature>, String> { ) -> Result<Vec<Signature>, String> {
let mut state = state.write().await; let mut state = state.write().await;
let signature = state.signatures.remove(idx); let signature = state.signatures.remove(idx);
let (new_signature, _) = signature.randomise(&state.params); let new = signature.randomise(&state.params);
state.signatures.insert(idx, new_signature); state.signatures.insert(idx, new);
Ok(state.signatures.clone()) Ok(state.signatures.clone())
} }
@@ -121,15 +117,14 @@ async fn prove_credential(
let state = state.read().await; let state = state.read().await;
if let Some(signature) = state.signatures.get(idx) { if let Some(signature) = state.signatures.get(idx) {
match coconut_interface::prove_bandwidth_credential( match coconut_interface::prove_credential(
&state.params, &state.params,
&verification_key, &verification_key,
signature, signature,
state.serial_number, &state.private_attributes,
state.binding_number,
) { ) {
Ok(theta) => Ok(theta), Ok(theta) => Ok(theta),
Err(e) => Err(format!("{:?}", e)), Err(e) => Err(format!("{}", e)),
} }
} else { } else {
Err("Got invalid Signature idx".to_string()) Err("Got invalid Signature idx".to_string())
@@ -149,15 +144,10 @@ async fn verify_credential(
let state = state.read().await; let state = state.read().await;
let public_attributes_bytes = vec![
state.voucher_value.to_bytes().to_vec(),
state.voucher_info.to_bytes().to_vec(),
];
let credential = Credential::new( let credential = Credential::new(
state.n_attributes, state.n_attributes,
theta, theta,
public_attributes_bytes, state.public_attributes_bytes.clone(),
state state
.signatures .signatures
.get(idx) .get(idx)
@@ -174,13 +164,11 @@ async fn get_credential(
) -> Result<Vec<Signature>, String> { ) -> Result<Vec<Signature>, String> {
let guard = state.read().await; let guard = state.read().await;
let parsed_urls = parse_url_validators(&validator_urls)?; let parsed_urls = parse_url_validators(&validator_urls)?;
let public_attributes = vec![guard.voucher_value, guard.voucher_info];
let private_attributes = vec![guard.serial_number, guard.binding_number];
let signature = obtain_aggregate_signature( let signature = obtain_aggregate_signature(
&guard.params, &guard.params,
&public_attributes, &guard.public_attributes,
&private_attributes, &guard.private_attributes,
&parsed_urls, &parsed_urls,
) )
.await .await
+7 -7
View File
@@ -1813,7 +1813,7 @@ decompress-response@^3.2.0, decompress-response@^3.3.0:
decompress-response@^4.2.0: decompress-response@^4.2.0:
version "4.2.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz"
integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
dependencies: dependencies:
mimic-response "^2.0.0" mimic-response "^2.0.0"
@@ -3130,7 +3130,7 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
mimic-response@^2.0.0: mimic-response@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz"
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
mimic-response@^3.1.0: mimic-response@^3.1.0:
@@ -4056,13 +4056,13 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
simple-concat@^1.0.0: simple-concat@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz"
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
simple-get@^3.0.3, simple-get@^3.1.0: simple-get@^3.0.3, simple-get@^3.1.0:
version "3.1.1" version "3.1.0"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55" resolved "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz"
integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA== integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
dependencies: dependencies:
decompress-response "^4.2.0" decompress-response "^4.2.0"
once "^1.3.1" once "^1.3.1"
@@ -4673,7 +4673,7 @@ wrap-ansi@^7.0.0:
wrappy@1: wrappy@1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
write-file-atomic@^3.0.0: write-file-atomic@^3.0.0:
-25
View File
@@ -1,25 +0,0 @@
# CLIENT INIT
NYMD_URL=https://sandbox-validator.nymtech.net
VALIDATOR_API=https://sandbox-validator.nymtech.net/api
MIXNET_CONTRACT=nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j
VESTING_CONTRACT=nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s
CURRENCY_PREFIX=nymt
CHAIN_ID=nym-sandbox
# USER DETAILS
USER_MNEMONIC=
USER_WALLET_ADDRESS=
# MIXNODE DETAILS
MIXNODE_IDENTITY=
MIXNODE_SPHINX_KEY=
MIXNODE_SIGNATURE=
MIXNODE_HOST="1.1.1.1"
MIXNODE_VERSION="0.12.1"
# GATEWAY DETAILS
GATEWAY_IDENTITY=
GATEWAY_SPHINX=
GATEAWAY_LOCATION=
GATEWAY_HOST="1.1.1.1"
GATEWAY_VERSION="0.12.1"
+24 -66
View File
@@ -1,83 +1,41 @@
{ {
"root": true, "root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"env": { "env": {
"browser": true,
"es6": true, "es6": true,
"node": true "node": true,
"mocha": true
}, },
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": { "parserOptions": {
"ecmaVersion": 2019, "ecmaVersion": 2018,
"sourceType": "module" "sourceType": "module"
}, },
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["prettier", "mocha"],
"extends": [
"airbnb-base",
"airbnb-typescript/base",
"prettier"],
"rules": { "rules": {
"prettier/prettier": "error", "no-console": "off",
"import/prefer-default-export": "off", "linebreak-style": "off",
"import/no-extraneous-dependencies": [ "quotes": [
"error", "error",
"double",
{ {
"devDependencies": [ "allowTemplateLiterals": true
"**/*.test.[jt]s",
"**/*.spec.[jt]s"
]
} }
], ],
"import/extensions": [ "keyword-spacing": [
"error", "error",
"ignorePackages",
{ {
"ts": "never", "before": true
"js": "never"
} }
],
"space-before-blocks": [
"error"
] ]
}, }
"overrides": [ }
{
"files": "**/*.ts",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.ts",
"**/*.spec.ts"
]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
]
}
-6
View File
@@ -1,6 +0,0 @@
{
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 2
}
+12 -6
View File
@@ -3,20 +3,26 @@ Nym Validator Client
A TypeScript client for interacting with CosmWasm smart contracts in Nym validators. A TypeScript client for interacting with CosmWasm smart contracts in Nym validators.
Running examples
-----------------
With the code checked out, `cd examples`. This folder contains runnable example code that will set up a blockchain and allow you to interact with it through the client.
Running tests Running tests
------------- -------------
The tests will be separated into three categories: unit, integration and mock.
Currently the command to run all tests:
``` ```
npm test npm test
``` ```
The tests require `.env.example` being renamed to `.env`. The variables and their values for these tests are currently pointing to the `nym-sandbox` environment. You can also trigger test execution with a test watcher. I don't have the centuries of life left to me that are needed to fight through the arcana of wiring up a working TypeScript mocha triggered execution setup, so for now my Cargo-based hack is:
`Tests are still in development` - the test libary is `jest` and the test script will execute currently with: `--coverage --verbosity false`
```
cargo watch -s "cd clients/validator && npm test"
```
It's ugly but works fine if you have Cargo installed. TypeScript setup help happily accepted here.
Generating Documentation Generating Documentation
------------------------ ------------------------
-7
View File
@@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFiles: ["dotenv/config"],
testTimeout: 20000
};
+3848
View File
File diff suppressed because it is too large Load Diff
+21 -27
View File
@@ -1,14 +1,15 @@
{ {
"name": "@nymproject/nym-validator-client", "name": "@nymproject/nym-validator-client",
"version": "0.19.0", "version": "0.18.0",
"description": "A TypeScript client for interacting with smart contracts in Nym validators", "description": "A TypeScript client for interacting with smart contracts in Nym validators",
"repository": "https://github.com/nymtech/nym", "repository": "https://github.com/nymtech/nym",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"scripts": { "scripts": {
"test": "jest --verbose false", "build": "tsc",
"lint": "eslint src", "test": "ts-mocha tests/**/*.test.ts",
"lint:fix": "eslint src --fix", "coverage": "nyc npm test",
"lint": "eslint \"**/*.ts\"",
"docs": "typedoc --out docs src/index.ts" "docs": "typedoc --out docs src/index.ts"
}, },
"keywords": [], "keywords": [],
@@ -19,32 +20,25 @@
], ],
"license": "Apache-2.0", "license": "Apache-2.0",
"devDependencies": { "devDependencies": {
"@types/jest": "27.4.0", "@types/chai": "^4.2.15",
"@typescript-eslint/eslint-plugin": "^5.7.0", "@types/expect": "^24.3.0",
"@typescript-eslint/parser": "^5.7.0", "@types/mocha": "^8.2.1",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
"chai": "^4.2.0",
"eslint": "^7.18.0", "eslint": "^7.18.0",
"eslint-config-airbnb": "^19.0.2", "mocha": "^8.2.1",
"eslint-config-airbnb-typescript": "^16.1.0", "moq.ts": "^7.2.0",
"eslint-config-prettier": "^8.3.0", "nyc": "^15.1.0",
"eslint-import-resolver-root-import": "^1.0.4", "ts-mocha": "^8.0.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.4.5",
"prettier": "^2.5.1",
"ts-jest": "^27.1.2",
"typedoc": "^0.20.27", "typedoc": "^0.20.27",
"typescript": "^4.5.4" "typescript": "^4.1.3"
}, },
"dependencies": { "dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.27.0-rc2",
"@cosmjs/crypto": "^0.27.0-rc2",
"@cosmjs/math": "^0.27.0-rc2",
"@cosmjs/proto-signing": "^0.27.0-rc2",
"@cosmjs/stargate": "^0.27.0-rc2",
"@cosmjs/tendermint-rpc": "^0.27.0-rc2",
"axios": "^0.21.1", "axios": "^0.21.1",
"cosmjs-types": "^0.4.0", "@cosmjs/cosmwasm-stargate": "^0.25.5",
"dotenv": "^10.0.0", "@cosmjs/stargate": "^0.25.5",
"moq.ts": "^7.3.4" "@cosmjs/math": "^0.25.5",
"@cosmjs/proto-signing": "^0.25.5"
} }
} }
+41
View File
@@ -0,0 +1,41 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"env": {
"es6": true,
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"no-console": "off",
"linebreak-style": "off",
"quotes": [
"error",
"double",
{
"allowTemplateLiterals": true
}
],
"keyword-spacing": [
"error",
{
"before": true
}
],
"space-before-blocks": [
"error"
]
}
}
+1
View File
@@ -0,0 +1 @@
15.0.1
+59
View File
@@ -0,0 +1,59 @@
import {GatewayBond, PagedGatewayResponse} from "../types";
import {INetClient} from "../net-client"
import {IQueryClient} from "../query-client";
import {VALIDATOR_API_GATEWAYS, VALIDATOR_API_PORT} from "../index";
import axios from "axios";
/**
* There are serious limits in smart contract systems, but we need to keep track of
* potentially thousands of nodes. GatewaysCache instances repeatedly make requests for
* paged data about what gateways exist, and keep them locally in memory so that they're
* available for querying.
**/
export default class GatewaysCache {
gateways: GatewayBond[]
client: INetClient | IQueryClient
perPage: number
constructor(client: INetClient | IQueryClient, perPage: number) {
this.client = client;
this.gateways = [];
this.perPage = perPage;
}
/// Makes repeated requests to assemble a full list of gateways.
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
/// returns true.
async refreshGateways(contractAddress: string): Promise<GatewayBond[]> {
let newGateways: GatewayBond[] = [];
let response: PagedGatewayResponse;
let next: string | undefined = undefined;
for (;;) {
response = await this.client.getGateways(contractAddress, this.perPage, next);
newGateways = newGateways.concat(response.nodes)
next = response.start_next_after;
// if `start_next_after` is not set, we're done
if (!next) {
break
}
}
this.gateways = newGateways
return newGateways;
}
/// Makes requests to assemble a full list of gateways from validator-api
async refreshValidatorAPIGateways(urls: string[]): Promise<GatewayBond[]> {
for (const url of urls) {
const validator_api_url = new URL(url);
validator_api_url.port = VALIDATOR_API_PORT;
validator_api_url.pathname += VALIDATOR_API_GATEWAYS;
const response = await axios.get(validator_api_url.toString());
if (response.status == 200) {
return response.data;
}
}
throw new Error("None of the provided validators seem to be alive")
}
}
+60
View File
@@ -0,0 +1,60 @@
import {MixNodeBond, PagedMixnodeResponse} from "../types";
import { INetClient } from "../net-client"
import {IQueryClient} from "../query-client";
import {VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT} from "../index";
import axios from "axios";
export { MixnodesCache };
/**
* There are serious limits in smart contract systems, but we need to keep track of
* potentially thousands of nodes. MixnodeCache instances repeatedly make requests for
* paged data about what mixnodes exist, and keep them locally in memory so that they're
* available for querying.
* */
export default class MixnodesCache {
mixNodes: MixNodeBond[]
client: INetClient | IQueryClient
perPage: number
constructor(client: INetClient | IQueryClient, perPage: number) {
this.client = client;
this.mixNodes = [];
this.perPage = perPage;
}
/// Makes repeated requests to assemble a full list of nodes.
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
// returns true.
async refreshMixNodes(contractAddress: string): Promise<MixNodeBond[]> {
let newMixnodes: MixNodeBond[] = [];
let response: PagedMixnodeResponse;
let next: string | undefined = undefined;
for (;;) {
response = await this.client.getMixNodes(contractAddress, this.perPage, next);
newMixnodes = newMixnodes.concat(response.nodes)
next = response.start_next_after;
// if `start_next_after` is not set, we're done
if (!next) {
break
}
}
this.mixNodes = newMixnodes
return this.mixNodes;
}
/// Makes requests to assemble a full list of mixnodes from validator-api
async refreshValidatorAPIMixNodes(urls: string[]): Promise<MixNodeBond[]> {
for (const url of urls) {
const validator_api_url = new URL(url);
validator_api_url.port = VALIDATOR_API_PORT;
validator_api_url.pathname += VALIDATOR_API_MIXNODES;
const response = await axios.get(validator_api_url.toString());
if (response.status == 200) {
return response.data;
}
}
throw new Error("None of the provided validators seem to be alive")
}
}
+38 -33
View File
@@ -1,68 +1,73 @@
import { Decimal } from '@cosmjs/math'; import { Decimal } from "@cosmjs/math";
import { Coin } from '@cosmjs/stargate'; import { Coin } from ".";
// NARROW NO-BREAK SPACE (U+202F) // NARROW NO-BREAK SPACE (U+202F)
const thinSpace = '\u202F'; const thinSpace = "\u202F";
export function printableCoin(coin?: Coin): string { export function printableCoin(coin?: Coin): string {
if (!coin) { if (!coin) {
return '0'; return "0";
} }
if (coin.denom.startsWith('u')) { if (coin.denom.startsWith("u")) {
const ticker = coin.denom.slice(1).toUpperCase(); const ticker = coin.denom.slice(1).toUpperCase();
return Decimal.fromAtomics(coin.amount, 6).toString() + thinSpace + ticker; return Decimal.fromAtomics(coin.amount, 6).toString() + thinSpace + ticker;
} } else {
return coin.amount + thinSpace + coin.denom; return coin.amount + thinSpace + coin.denom;
}
} }
export function printableBalance(balance?: readonly Coin[]): string { export function printableBalance(balance?: readonly Coin[]): string {
if (!balance || balance.length === 0) return ''; if (!balance || balance.length === 0) return "";
return balance.map(printableCoin).join(', '); return balance.map(printableCoin).join(", ");
} }
// converts display amount, such as "12.0346" to its native token representation, // converts display amount, such as "12.0346" to its native token representation,
// with 6 fractional digits. So in that case it would result in "12034600" // with 6 fractional digits. So in that case it would result in "12034600"
// Basically does the same job as `displayAmountToNative` but without the requirement // Basically does the same job as `displayAmountToNative` but without the requirement
// of having the coinMap // of having the coinMap
export function printableBalanceToNative(amountToDisplay: string): string { export function printableBalanceToNative(amountToDisplay: string): string {
const decimalAmount = Decimal.fromUserInput(amountToDisplay, 6); const decimalAmount = Decimal.fromUserInput(amountToDisplay, 6);
return decimalAmount.atomics; return decimalAmount.atomics;
} }
// reciprocal of `printableBalanceToNative`, takes, for example 10000000 and returns 10 // reciprocal of `printableBalanceToNative`, takes, for example 10000000 and returns 10
export function nativeToPrintable(nativeValue: string): string { export function nativeToPrintable(nativeValue: string): string {
return Decimal.fromAtomics(nativeValue, 6).toString(); return Decimal.fromAtomics(nativeValue, 6).toString()
} }
export interface MappedCoin { export interface MappedCoin {
readonly denom: string; readonly denom: string;
readonly fractionalDigits: number; readonly fractionalDigits: number;
} }
export interface CoinMap { export interface CoinMap {
readonly [key: string]: MappedCoin; readonly [key: string]: MappedCoin;
} }
export function nativeCoinToDisplay(coin: Coin, coinMap: CoinMap): Coin { export function nativeCoinToDisplay(coin: Coin, coinMap: CoinMap): Coin {
if (!coinMap) return coin; if (!coinMap) return coin;
const coinToDisplay = coinMap[coin.denom]; const coinToDisplay = coinMap[coin.denom];
if (!coinToDisplay) return coin; if (!coinToDisplay) return coin;
const amountToDisplay = Decimal.fromAtomics(coin.amount, coinToDisplay.fractionalDigits).toString(); const amountToDisplay = Decimal.fromAtomics(coin.amount, coinToDisplay.fractionalDigits).toString();
return { denom: coinToDisplay.denom, amount: amountToDisplay }; return { denom: coinToDisplay.denom, amount: amountToDisplay };
} }
// display amount is eg "12.0346", return is in native tokens // display amount is eg "12.0346", return is in native tokens
// with 6 fractional digits, this would be eg. "12034600" // with 6 fractional digits, this would be eg. "12034600"
export function displayAmountToNative(amountToDisplay: string, coinMap: CoinMap, nativeDenom: string): string { export function displayAmountToNative(
const fractionalDigits = coinMap[nativeDenom]?.fractionalDigits; amountToDisplay: string,
if (fractionalDigits) { coinMap: CoinMap,
// use https://github.com/CosmWasm/cosmjs/blob/v0.22.2/packages/math/src/decimal.ts nativeDenom: string,
const decimalAmount = Decimal.fromUserInput(amountToDisplay, fractionalDigits); ): string {
return decimalAmount.atomics; const fractionalDigits = coinMap[nativeDenom]?.fractionalDigits;
} if (fractionalDigits) {
// use https://github.com/CosmWasm/cosmjs/blob/v0.22.2/packages/math/src/decimal.ts
const decimalAmount = Decimal.fromUserInput(amountToDisplay, fractionalDigits);
return decimalAmount.atomics;
}
return amountToDisplay; return amountToDisplay;
} }
File diff suppressed because it is too large Load Diff
+207
View File
@@ -0,0 +1,207 @@
import { SigningCosmWasmClient, SigningCosmWasmClientOptions } from "@cosmjs/cosmwasm-stargate";
import {
Delegation,
GatewayOwnershipResponse,
MixOwnershipResponse, PagedGatewayDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse,
StateParams
} from "./types";
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
import { Coin, StdFee } from "@cosmjs/stargate";
import { BroadcastTxResponse } from "@cosmjs/stargate"
import { nymGasLimits, nymGasPrice } from "./stargate-helper"
import {
ExecuteResult,
InstantiateOptions,
InstantiateResult,
MigrateResult,
UploadMeta,
UploadResult
} from "@cosmjs/cosmwasm-stargate";
export interface INetClient {
clientAddress: string;
getBalance(address: string, denom: string): Promise<Coin | null>;
getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse>;
getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse>;
getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse>
getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation>
getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse>
getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation>
ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(contractAddress: string): Promise<StateParams>;
signAndBroadcast(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo?: string): Promise<BroadcastTxResponse>;
executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult>;
instantiate(senderAddress: string, codeId: number, initMsg: Record<string, unknown>, label: string, options?: InstantiateOptions): Promise<InstantiateResult>;
sendTokens(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise<BroadcastTxResponse>;
upload(senderAddress: string, wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise<UploadResult>;
changeValidator(newUrl: string): Promise<void>
}
/**
* Takes care of network communication between this code and the validator.
* Depends on `SigningCosmWasClient`, which signs all requests using keypairs
* derived from on bech32 mnemonics.
*
* Wraps several methods from CosmWasmSigningClient so we can mock them for
* unit testing.
*/
export default class NetClient implements INetClient {
clientAddress: string;
private cosmClient: SigningCosmWasmClient;
// helpers for changing validators without having to remake the wallet
private readonly wallet: DirectSecp256k1HdWallet;
private readonly signerOptions: SigningCosmWasmClientOptions;
private constructor(clientAddress: string, cosmClient: SigningCosmWasmClient, wallet: DirectSecp256k1HdWallet, signerOptions: SigningCosmWasmClientOptions) {
this.clientAddress = clientAddress;
this.cosmClient = cosmClient;
this.wallet = wallet;
this.signerOptions = signerOptions;
}
public static async connect(wallet: DirectSecp256k1HdWallet, url: string, prefix: string): Promise<INetClient> {
const [{address}] = await wallet.getAccounts();
const signerOptions: SigningCosmWasmClientOptions = {
gasPrice: nymGasPrice(prefix),
gasLimits: nymGasLimits,
};
const client = await SigningCosmWasmClient.connectWithSigner(url, wallet, signerOptions);
return new NetClient(address, client, wallet, signerOptions);
}
async changeValidator(url: string): Promise<void> {
this.cosmClient = await SigningCosmWasmClient.connectWithSigner(url, this.wallet, this.signerOptions);
}
public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit, start_after}});
}
}
public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit, start_after}});
}
}
public getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegations: {
mix_identity: mixIdentity,
limit
}
});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegations: {
mix_identity: mixIdentity,
limit,
start_after
}
});
}
}
public getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegation: {
mix_identity: mixIdentity,
address: delegatorAddress
}
});
}
public getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegations: {
gateway_identity: gatewayIdentity,
limit
}
});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegations: {
gateway_identity: gatewayIdentity,
limit,
start_after
}
});
}
}
public getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegation: {
gateway_identity: gatewayIdentity,
address: delegatorAddress
}
});
}
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_mixnode: {address}});
}
public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_gateway: {address}});
}
public getBalance(address: string, denom: string): Promise<Coin | null> {
return this.cosmClient.getBalance(address, denom);
}
public getStateParams(contractAddress: string): Promise<StateParams> {
return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
}
public executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult> {
return this.cosmClient.execute(senderAddress, contractAddress, handleMsg, memo, transferAmount);
}
public signAndBroadcast(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo?: string): Promise<BroadcastTxResponse> {
return this.cosmClient.signAndBroadcast(signerAddress, messages, fee, memo)
}
public sendTokens(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise<BroadcastTxResponse> {
return this.cosmClient.sendTokens(senderAddress, recipientAddress, transferAmount, memo);
}
public upload(senderAddress: string, wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise<UploadResult> {
return this.cosmClient.upload(senderAddress, wasmCode, meta, memo);
}
public instantiate(senderAddress: string, codeId: number, initMsg: Record<string, unknown>, label: string, options?: InstantiateOptions): Promise<InstantiateResult> {
return this.cosmClient.instantiate(senderAddress, codeId, initMsg, label, options);
}
public migrate(senderAddress: string, contractAddress: string, codeId: number, migrateMsg: Record<string, unknown>, memo?: string): Promise<MigrateResult> {
return this.cosmClient.migrate(senderAddress, contractAddress, codeId, migrateMsg, memo)
}
}
-175
View File
@@ -1,175 +0,0 @@
/*
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
import { JsonObject } from '@cosmjs/cosmwasm-stargate/build/queries';
// eslint-disable-next-line import/no-cycle
import { INymdQuery } from './query-client';
import {
ContractStateParams,
Delegation,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingStatus,
} from './types';
interface SmartContractQuery {
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
export default class NymdQuerier implements INymdQuery {
client: SmartContractQuery;
constructor(client: SmartContractQuery) {
this.client = client;
}
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_contract_version: {},
});
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mix_nodes: {
limit,
start_after: startAfter,
},
});
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_gateways: {
limit,
start_after: startAfter,
},
});
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
owns_mixnode: {
address,
},
});
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
owns_gateway: {
address,
},
});
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
return this.client.queryContractSmart(mixnetContractAddress, {
state_params: {},
});
}
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_all_network_delegations: {
start_after: startAfter,
limit,
},
});
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mixnode_delegations: {
mix_identity: mixIdentity,
start_after: startAfter,
limit,
},
});
}
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_delegator_delegations: {
delegator,
start_after: startAfter,
limit,
},
});
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_delegation_details: {
mix_identity: mixIdentity,
delegator,
},
});
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.client.queryContractSmart(mixnetContractAddress, {
layer_distribution: {},
});
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_reward_pool: {},
});
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_circulating_supply: {},
});
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_interval_reward_percent: {},
});
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_sybil_resistance_percent: {},
});
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_rewarding_status: {
mix_identity: mixIdentity,
rewarding_interval_nonce: rewardingIntervalNonce,
},
});
}
}
+139 -204
View File
@@ -1,212 +1,147 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'; import { Coin } from "@cosmjs/stargate";
import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { import {
Account, Delegation,
Block, GatewayOwnershipResponse,
Coin, MixOwnershipResponse, PagedGatewayDelegationsResponse,
DeliverTxResponse, PagedGatewayResponse, PagedMixDelegationsResponse,
IndexedTx, PagedMixnodeResponse,
SearchTxFilter, StateParams
SearchTxQuery, } from "./types";
SequenceResponse,
} from '@cosmjs/stargate';
import { JsonObject } from '@cosmjs/cosmwasm-stargate/build/queries';
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient';
// eslint-disable-next-line import/no-cycle
import NymdQuerier from './nymd-querier';
import {
ContractStateParams,
Delegation,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNodeBond,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingStatus,
} from './types';
import ValidatorApiQuerier, { IValidatorApiQuery } from './validator-api-querier';
export interface ICosmWasmQuery { export interface IQueryClient {
// methods exposed by `CosmWasmClient` getBalance(address: string, stakeDenom: string): Promise<Coin | null>;
getChainId(): Promise<string>;
getHeight(): Promise<number>; getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse>;
getAccount(searchAddress: string): Promise<Account | null>;
getSequence(address: string): Promise<SequenceResponse>; getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse>;
getBlock(height?: number): Promise<Block>;
getBalance(address: string, searchDenom: string): Promise<Coin>; getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse>
getTx(id: string): Promise<IndexedTx | null>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>; getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation>
disconnect(): void;
broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise<DeliverTxResponse>; getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse>
getCodes(): Promise<readonly Code[]>;
getCodeDetails(codeId: number): Promise<CodeDetails>; getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation>
getContracts(codeId: number): Promise<readonly string[]>;
getContract(address: string): Promise<Contract>; ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse>;
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>; ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
getStateParams(contractAddress: string): Promise<StateParams>;
changeValidator(newUrl: string): Promise<void>
} }
export interface INymdQuery { /**
// nym-specific implemented inside NymQuerier * Takes care of network communication between this code and the validator.
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>; * Depends on `SigningCosmWasClient`, which signs all requests using keypairs
* derived from on bech32 mnemonics.
*
* Wraps several methods from CosmWasmSigningClient so we can mock them for
* unit testing.
*/
export default class QueryClient implements IQueryClient {
private cosmClient: CosmWasmClient;
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse>; private constructor(cosmClient: CosmWasmClient) {
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse>; this.cosmClient = cosmClient;
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>; }
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams>;
getAllNetworkDelegationsPaged( public static async connect(url: string): Promise<IQueryClient> {
mixnetContractAddress: string, const client = await CosmWasmClient.connect(url)
limit?: number, return new QueryClient(client)
startAfter?: [string, string], }
): Promise<PagedAllDelegationsResponse>;
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse>;
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse>;
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation>;
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution>; async changeValidator(url: string): Promise<void> {
getRewardPool(mixnetContractAddress: string): Promise<string>; this.cosmClient = await CosmWasmClient.connect(url)
getCirculatingSupply(mixnetContractAddress: string): Promise<string>; }
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number>;
getSybilResistancePercent(mixnetContractAddress: string): Promise<number>; public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
getRewardingStatus( if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
mixnetContractAddress: string, return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit}});
mixIdentity: string, } else {
rewardingIntervalNonce: number, return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit, start_after}});
): Promise<RewardingStatus>; }
} }
export interface IQueryClient extends ICosmWasmQuery, INymdQuery, IValidatorApiQuery {} public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
export default class QueryClient extends CosmWasmClient implements IQueryClient { return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit}});
private nymdQuerier: NymdQuerier; } else {
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit, start_after}});
private validatorApiQuerier: ValidatorApiQuerier; }
}
private constructor(tmClient: Tendermint34Client, validatorApiUrl: string) {
super(tmClient); public getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse> {
this.nymdQuerier = new NymdQuerier(this); if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl); return this.cosmClient.queryContractSmart(contractAddress, {
} get_mix_delegations: {
mix_identity: mixIdentity,
public static async connectWithNym(nymdUrl: string, validatorApiUrl: string): Promise<QueryClient> { limit
const tmClient = await Tendermint34Client.connect(nymdUrl); }
return new QueryClient(tmClient, validatorApiUrl); });
} } else {
return this.cosmClient.queryContractSmart(contractAddress, {
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> { get_mix_delegations: {
return this.nymdQuerier.getContractVersion(mixnetContractAddress); mix_identity: mixIdentity,
} limit,
start_after
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> { }
return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter); });
} }
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter); public getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation> {
} return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegation: {
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> { mix_identity: mixIdentity,
return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address); address: delegatorAddress
} }
});
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> { }
return this.nymdQuerier.ownsGateway(mixnetContractAddress, address);
} public getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> { return this.cosmClient.queryContractSmart(contractAddress, {
return this.nymdQuerier.getStateParams(mixnetContractAddress); get_gateway_delegations: {
} gateway_identity: gatewayIdentity,
limit
getAllNetworkDelegationsPaged( }
mixnetContractAddress: string, });
limit?: number, } else {
startAfter?: [string, string], return this.cosmClient.queryContractSmart(contractAddress, {
): Promise<PagedAllDelegationsResponse> { get_gateway_delegations: {
return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter); gateway_identity: gatewayIdentity,
} limit,
start_after
getMixNodeDelegationsPaged( }
mixnetContractAddress: string, });
mixIdentity: string, }
limit?: number, }
startAfter?: string,
): Promise<PagedMixDelegationsResponse> { public getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation> {
return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter); return this.cosmClient.queryContractSmart(contractAddress, {
} get_gateway_delegation: {
gateway_identity: gatewayIdentity,
getDelegatorDelegationsPaged( address: delegatorAddress
mixnetContractAddress: string, }
delegator: string, });
limit?: number, }
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse> { public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter); return this.cosmClient.queryContractSmart(contractAddress, {owns_mixnode: {address}});
} }
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> { public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator); return this.cosmClient.queryContractSmart(contractAddress, {owns_gateway: {address}});
} }
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> { public getBalance(address: string, stakeDenom: string): Promise<Coin | null> {
return this.nymdQuerier.getLayerDistribution(mixnetContractAddress); return this.cosmClient.getBalance(address, stakeDenom);
} }
getRewardPool(mixnetContractAddress: string): Promise<string> { public getStateParams(contractAddress: string): Promise<StateParams> {
return this.nymdQuerier.getRewardPool(mixnetContractAddress); return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
} }
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getIntervalRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
}
getCachedGateways(): Promise<GatewayBond[]> {
return this.validatorApiQuerier.getCachedGateways();
}
getCachedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getActiveMixnodes();
}
getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getRewardedMixnodes();
}
} }
-483
View File
@@ -1,483 +0,0 @@
import {
ExecuteResult,
InstantiateOptions,
InstantiateResult,
MigrateResult,
SigningCosmWasmClient,
SigningCosmWasmClientOptions,
UploadResult,
} from '@cosmjs/cosmwasm-stargate';
import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing';
import { Coin, DeliverTxResponse, SignerData, StdFee } from '@cosmjs/stargate';
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
import { ChangeAdminResult } from '@cosmjs/cosmwasm-stargate/build/signingcosmwasmclient';
import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { nymGasPrice } from './stargate-helper';
import { IQueryClient } from './query-client';
import NymdQuerier from './nymd-querier';
import {
ContractStateParams,
Delegation,
Gateway,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNode,
MixNodeBond,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingStatus,
} from './types';
import ValidatorApiQuerier from './validator-api-querier';
// methods exposed by `SigningCosmWasmClient`
export interface ICosmWasmSigning {
simulate(signerAddress: string, messages: readonly EncodeObject[], memo: string | undefined): Promise<number>;
upload(
senderAddress: string,
wasmCode: Uint8Array,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<UploadResult>;
instantiate(
senderAddress: string,
codeId: number,
msg: Record<string, unknown>,
label: string,
fee: StdFee | 'auto' | number,
options?: InstantiateOptions,
): Promise<InstantiateResult>;
updateAdmin(
senderAddress: string,
contractAddress: string,
newAdmin: string,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<ChangeAdminResult>;
clearAdmin(
senderAddress: string,
contractAddress: string,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<ChangeAdminResult>;
migrate(
senderAddress: string,
contractAddress: string,
codeId: number,
migrateMsg: Record<string, unknown>,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<MigrateResult>;
execute(
senderAddress: string,
contractAddress: string,
msg: Record<string, unknown>,
fee: StdFee | 'auto' | number,
memo?: string,
funds?: readonly Coin[],
): Promise<ExecuteResult>;
sendTokens(
senderAddress: string,
recipientAddress: string,
amount: readonly Coin[],
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
delegateTokens(
delegatorAddress: string,
validatorAddress: string,
amount: Coin,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
undelegateTokens(
delegatorAddress: string,
validatorAddress: string,
amount: Coin,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
withdrawRewards(
delegatorAddress: string,
validatorAddress: string,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
signAndBroadcast(
signerAddress: string,
messages: readonly EncodeObject[],
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
sign(
signerAddress: string,
messages: readonly EncodeObject[],
fee: StdFee,
memo: string,
explicitSignerData?: SignerData,
): Promise<TxRaw>;
}
export interface INymSigning {
clientAddress: string;
}
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning {
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
ownerSignature: string,
pledge: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
unbondMixNode(mixnetContractAddress: string, fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult>;
bondGateway(
mixnetContractAddress: string,
gateway: Gateway,
ownerSignature: string,
pledge: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
unbondGateway(mixnetContractAddress: string, fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult>;
delegateToMixNode(
mixnetContractAddress: string,
mixIdentity: string,
amount: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
undelegateFromMixNode(
mixnetContractAddress: string,
mixIdentity: string,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
updateMixnodeConfig(
mixnetContractAddress: string,
mixIdentity: string,
profitMarginPercent: number,
fee: StdFee | 'auto' | number,
): Promise<ExecuteResult>;
updateContractStateParams(
mixnetContractAddress: string,
newParams: ContractStateParams,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
// I don't see any point in exposing rewarding / vesting-related (INSIDE mixnet contract, like "BondMixnodeOnBehalf")
// functionalities in our typescript client. However, if for some reason, we find we need them
// they're rather trivial to add.
}
export default class SigningClient extends SigningCosmWasmClient implements ISigningClient {
private nymdQuerier: NymdQuerier;
private validatorApiQuerier: ValidatorApiQuerier;
clientAddress: string;
private constructor(
clientAddress: string,
validatorApiUrl: string,
tmClient: Tendermint34Client,
wallet: DirectSecp256k1HdWallet,
signerOptions: SigningCosmWasmClientOptions,
) {
super(tmClient, wallet, signerOptions);
this.clientAddress = clientAddress;
this.nymdQuerier = new NymdQuerier(this);
this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl);
}
public static async connectWithNymSigner(
wallet: DirectSecp256k1HdWallet,
nymdUrl: string,
validatorApiUrl: string,
prefix: string,
): Promise<SigningClient> {
const [{ address }] = await wallet.getAccounts();
const signerOptions: SigningCosmWasmClientOptions = {
gasPrice: nymGasPrice(prefix),
};
const tmClient = await Tendermint34Client.connect(nymdUrl);
return new SigningClient(address, validatorApiUrl, tmClient, wallet, signerOptions);
}
// query related:
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
return this.nymdQuerier.getContractVersion(mixnetContractAddress);
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address);
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nymdQuerier.ownsGateway(mixnetContractAddress, address);
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
return this.nymdQuerier.getStateParams(mixnetContractAddress);
}
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
}
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse> {
return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.nymdQuerier.getLayerDistribution(mixnetContractAddress);
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.nymdQuerier.getRewardPool(mixnetContractAddress);
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getIntervalRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
}
getCachedGateways(): Promise<GatewayBond[]> {
return this.validatorApiQuerier.getCachedGateways();
}
getCachedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getActiveMixnodes();
}
getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getRewardedMixnodes();
}
// signing related:
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
ownerSignature: string,
pledge: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Bonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
bond_mixnode: {
mix_node: mixNode,
owner_signature: ownerSignature,
},
},
fee,
memo,
[pledge],
);
}
unbondMixNode(
mixnetContractAddress: string,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Unbonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
unbond_mixnode: {},
},
fee,
memo,
);
}
bondGateway(
mixnetContractAddress: string,
gateway: Gateway,
ownerSignature: string,
pledge: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default Gateway Bonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
bond_gateway: {
gateway,
owner_signature: ownerSignature,
},
},
fee,
memo,
[pledge],
);
}
unbondGateway(
mixnetContractAddress: string,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default Gateway Unbonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
unbond_gateway: {},
},
fee,
memo,
);
}
delegateToMixNode(
mixnetContractAddress: string,
mixIdentity: string,
amount: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Delegation from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
delegate_to_mixnode: {
mix_identity: mixIdentity,
},
},
fee,
memo,
[amount],
);
}
undelegateFromMixNode(
mixnetContractAddress: string,
mixIdentity: string,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Undelegation from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
undelegate_from_mixnode: {
mix_identity: mixIdentity,
},
},
fee,
memo,
);
}
updateMixnodeConfig(
mixnetContractAddress: string,
mixIdentity: string,
profitMarginPercent: number,
fee: StdFee | 'auto' | number,
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{ update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_identity: mixIdentity } },
fee,
);
}
updateContractStateParams(
mixnetContractAddress: string,
newParams: ContractStateParams,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default Contract State Params Update from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
update_contract_state_params: newParams,
},
fee,
memo,
);
}
}
+18 -17
View File
@@ -1,25 +1,26 @@
import axios from 'axios'; import axios from "axios";
import { GasPrice } from '@cosmjs/stargate'; import { GasLimits, GasPrice } from "@cosmjs/stargate";
import { CosmWasmFeeTable, defaultGasLimits } from "@cosmjs/cosmwasm-stargate";
const mainnetPrefix = 'n'; export const nymGasLimits: GasLimits<CosmWasmFeeTable> = {
const mainnetDenom = 'nym'; ...defaultGasLimits,
upload: 2_500_000,
init: 500_000,
migrate: 200_000,
exec: 250_000,
send: 80_000,
changeAdmin: 80_000,
};
export function nymGasPrice(prefix: string): GasPrice { export function nymGasPrice(prefix: string): GasPrice {
if (typeof prefix === 'string') {
if (prefix === mainnetPrefix) {
prefix = mainnetDenom;
}
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here. return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
}
else {
throw new Error(`${prefix} is not of type string`);
}
} }
export const downloadWasm = async (url: string): Promise<Uint8Array> => { export const downloadWasm = async (url: string): Promise<Uint8Array> => {
const r = await axios.get(url, { responseType: 'arraybuffer' }); const r = await axios.get(url, {responseType: "arraybuffer"});
if (r.status !== 200) { if (r.status !== 200) {
throw new Error(`Download error: ${r.status}`); throw new Error(`Download error: ${r.status}`);
} }
return r.data; return r.data;
}; };

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