Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33c162f3b4 | |||
| 2809c23722 | |||
| 66830d0a8d | |||
| 802449d8af | |||
| 5dd4245d4c | |||
| c49498a669 | |||
| 7d123b9fce | |||
| 3c76b8386a | |||
| bdb021994e | |||
| f732d83479 | |||
| 221d042a21 | |||
| 4dea20e15b | |||
| 6ae54b2f89 | |||
| fe2386eab2 | |||
| 2a551610ae | |||
| b00fa15b55 | |||
| 4d904257a9 | |||
| f3a192a023 | |||
| c67c114af6 | |||
| 87c8b512b0 | |||
| 36d8502fed | |||
| ec2bf0beaf | |||
| 36b20d4e6e | |||
| 9554cebb2b | |||
| b9c4922e1a | |||
| 65ef3f02e7 | |||
| a75ca9f114 | |||
| aa2410c99b | |||
| c1df8eef62 | |||
| f5726a07f3 | |||
| 639fb802a4 | |||
| 9b945acba3 | |||
| 70a28d3767 | |||
| 533f6c5b24 | |||
| fba653fcfd | |||
| 22c0669f5d | |||
| edfd9531af | |||
| bbd35feb81 | |||
| bd26bc192e | |||
| 39af6abec7 | |||
| d1563c079e | |||
| 8250dca8be | |||
| 5bf0a14c20 | |||
| 6a3d10bab7 | |||
| 842b58751a | |||
| 544355bbfd | |||
| bd64a29932 | |||
| ed2cf1ced6 | |||
| 890b0bb677 | |||
| 09efb62fa1 | |||
| 4e80c57076 | |||
| 8c2361757e | |||
| 0eaad032f6 | |||
| 371467cede | |||
| 9110a8eefd | |||
| 2e8a0e9a72 | |||
| b068dde7c7 | |||
| d93918e99b |
+1
-1
@@ -36,5 +36,5 @@ Cargo.* @durch @futurechimp @jstuczyn @neacsu
|
||||
|
||||
# Explorer and wallet should probably get looked by the product team
|
||||
/explorer/ @nymtech/product
|
||||
/nym-wallet/ @nymtech/product
|
||||
/tauri-wallet/ @nymtech/product
|
||||
/wallet-web/ @nymtech/product
|
||||
|
||||
+70
-36
@@ -1,32 +1,16 @@
|
||||
name: Continuous integration
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'explorer/**'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'explorer/**'
|
||||
on: [push, pull_request]
|
||||
|
||||
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/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.os == 'windows-latest' }}
|
||||
strategy:
|
||||
matrix:
|
||||
rust: [stable, beta, nightly]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
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
|
||||
@@ -61,13 +45,6 @@ jobs:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
name: Clippy checks
|
||||
# if: matrix.os == 'ubuntu-latest' && matrix.rust == 'stable'
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features
|
||||
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
@@ -82,21 +59,78 @@ jobs:
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Build all binaries with coconut enabled
|
||||
# BUILD
|
||||
- name: Build gateway with coconut feature
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
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
|
||||
with:
|
||||
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
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
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,50 +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"
|
||||
},
|
||||
|
||||
{
|
||||
"os":"ubuntu-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
{
|
||||
"os":"macos-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
|
||||
{
|
||||
"os":"ubuntu-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"pull_request"
|
||||
},
|
||||
{
|
||||
"os":"macos-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"pull_request"
|
||||
}
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -11,7 +11,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-runner-linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rsync
|
||||
@@ -22,8 +22,6 @@ jobs:
|
||||
node-version: '14'
|
||||
- run: npm install
|
||||
continue-on-error: true
|
||||
- name: Set environment from the example
|
||||
run: cp .env.prod .env
|
||||
- run: npm run test
|
||||
continue-on-error: true
|
||||
- run: npm run build
|
||||
@@ -37,7 +35,7 @@ jobs:
|
||||
SOURCE: "explorer/dist/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
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/"
|
||||
- name: Keybase - Node Install
|
||||
run: npm install
|
||||
@@ -46,7 +44,6 @@ jobs:
|
||||
env:
|
||||
NYM_PROJECT_NAME: "Network Explorer"
|
||||
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_BRANCH: "${GITHUB_REF##*/}"
|
||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
name: Publish Nym Wallet
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- nym-wallet-*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run a one-line script
|
||||
run: echo Hello, world!
|
||||
@@ -1,18 +1,18 @@
|
||||
name: Webdriverio tests for nym wallet
|
||||
|
||||
on:
|
||||
push:
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "nym-wallet/**"
|
||||
|
||||
- 'tauri-wallet/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nym-wallet
|
||||
working-directory: tauri-wallet
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: wallet tests
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -30,8 +30,8 @@ jobs:
|
||||
- name: Install minimal stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v1
|
||||
@@ -39,32 +39,32 @@ jobs:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Install yarn for building application
|
||||
run: yarn install
|
||||
run: yarn install
|
||||
|
||||
- name: Build application
|
||||
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
|
||||
cd target/release/
|
||||
(test -f nym-wallet && echo nym binary exists) || echo wallet does not exist
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
working-directory: nym-wallet/webdriver
|
||||
working-directory: tauri-wallet/webdriver
|
||||
|
||||
- name: Remove existing user datafile
|
||||
uses: JesseTG/rm@v1.0.2
|
||||
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
|
||||
id: create-json
|
||||
uses: jsdaniell/create-json@1.1.2
|
||||
with:
|
||||
name: "user-data.json"
|
||||
json: ${{ secrets.WALLET_USERDATA }}
|
||||
dir: "nym-wallet/webdriver/common/data/"
|
||||
name: "user-data.json"
|
||||
json: ${{ secrets.WALLET_USERDATA }}
|
||||
dir: 'tauri-wallet/webdriver/common/data/'
|
||||
|
||||
- name: Install tauri-driver
|
||||
uses: actions-rs/cargo@v1
|
||||
@@ -73,5 +73,5 @@ jobs:
|
||||
args: tauri-driver
|
||||
|
||||
- name: Launch tests
|
||||
run: xvfb-run yarn test:runall
|
||||
working-directory: nym-wallet/webdriver
|
||||
run: xvfb-run yarn test:newuser
|
||||
working-directory: tauri-wallet/webdriver
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
|
||||
> :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**
|
||||
> `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 }}
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
name: Generate TS types
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "explorer/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "explorer/**"
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
nym-wallet-types:
|
||||
tauri-wallet-types:
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
@@ -20,10 +13,10 @@ jobs:
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Generate TS
|
||||
run: cd nym-wallet/src-tauri && cargo test
|
||||
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: '["nym-wallet"]'
|
||||
message: "[ci skip] Generate TS types"
|
||||
add: '["tauri-wallet"]'
|
||||
message: '[ci skip] Generate TS types'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,9 +1,6 @@
|
||||
name: Wasm Client
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'explorer/**'
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
wasm:
|
||||
@@ -19,11 +16,10 @@ jobs:
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
# token credentials (non-coconut) don't work for wasm right now
|
||||
# - uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: build
|
||||
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@@ -45,4 +41,4 @@ jobs:
|
||||
# - uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: clippy
|
||||
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
|
||||
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
|
||||
@@ -1,3 +0,0 @@
|
||||
unreleased=true
|
||||
future-release=v0.12.0
|
||||
since-tag=v0.11.0
|
||||
+1
-7
@@ -11,6 +11,7 @@ target
|
||||
/.vscode/settings.json
|
||||
validator/.vscode
|
||||
sample-configs/validator-config.toml
|
||||
.vscode
|
||||
scripts/deploy_qa.sh
|
||||
scripts/run_gate.sh
|
||||
scripts/run_mix.sh
|
||||
@@ -24,15 +25,8 @@ v6-topology.json
|
||||
/explorer/downloads/topology.json
|
||||
/explorer/public/downloads/mixmining.json
|
||||
/explorer/public/downloads/topology.json
|
||||
/nym-wallet/dist/*
|
||||
/clients/validator/examples/nym-driver-example/current-contract.txt
|
||||
validator-api/v4.json
|
||||
validator-api/v6.json
|
||||
**/node_modules
|
||||
validator-api/keypair
|
||||
contracts/mixnet/code_id
|
||||
contracts/mixnet/Justfile
|
||||
contracts/mixnet/Makefile
|
||||
validator-config
|
||||
*.patch
|
||||
validator-api-config.toml
|
||||
@@ -1 +0,0 @@
|
||||
2.7.5
|
||||
+879
-228
File diff suppressed because it is too large
Load Diff
Generated
+31
-521
@@ -93,12 +93,6 @@ version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.1"
|
||||
@@ -234,20 +228,6 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "az"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
|
||||
|
||||
[[package]]
|
||||
name = "bandwidth-claim-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base-x"
|
||||
version = "0.2.8"
|
||||
@@ -316,18 +296,6 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.8.1"
|
||||
@@ -347,7 +315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcd555c66291d5f836dbb6883b48660ece810fe25a31f3bdfb911945dff2691f"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec 0.7.1",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"constant_time_eq",
|
||||
@@ -440,24 +408,12 @@ version = "3.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
|
||||
|
||||
[[package]]
|
||||
name = "byte-slice-cast"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca0796d76a983651b4a0ddda16203032759f2fd9103d9181f7c65c06ee8872e6"
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
@@ -620,7 +576,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "0.12.0"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"crypto",
|
||||
@@ -876,7 +832,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cosmos-sdk-proto"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edb5204c6ddc4352c74297638b5561f2929d6334866c156e5f3c75e1e1a1436a"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"prost-types",
|
||||
@@ -886,7 +843,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cosmrs"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d31147fe89e547e74e2692e4bec35387e1ea406fe8cfb14c0ea177b58fd2a8a9"
|
||||
dependencies = [
|
||||
"bip32",
|
||||
"cosmos-sdk-proto",
|
||||
@@ -907,9 +865,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a380b87642204557629c9b72988c47b55fbfe6d474960adba56b22331504956a"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
@@ -920,18 +877,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "866713b2fe13f23038c7d8824c3059d1f28dd94685fb406d1533c4eeeefeefae"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8dbb9939b31441dfa9af3ec9740c8a24d585688401eff1b6b386abb7ad0d10a8"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
@@ -981,8 +936,6 @@ name = "credentials"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"coconut-interface",
|
||||
"crypto",
|
||||
"network-defaults",
|
||||
"thiserror",
|
||||
"url",
|
||||
"validator-client",
|
||||
@@ -1092,7 +1045,6 @@ dependencies = [
|
||||
"blake3",
|
||||
"bs58",
|
||||
"cipher",
|
||||
"config",
|
||||
"digest 0.9.0",
|
||||
"ed25519-dalek",
|
||||
"generic-array 0.14.4",
|
||||
@@ -1102,7 +1054,6 @@ dependencies = [
|
||||
"nymsphinx-types",
|
||||
"pemstore",
|
||||
"rand 0.7.3",
|
||||
"subtle-encoding",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
@@ -1218,17 +1169,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw-storage-plus"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3b8b840947313c1a1cccf056836cd79a60b4526bdcd6582995be37dc97be4ae"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.10.2"
|
||||
@@ -1624,26 +1564,6 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6"
|
||||
dependencies = [
|
||||
"enum-iterator-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator-derive"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum_kind"
|
||||
version = "0.2.1"
|
||||
@@ -1678,49 +1598,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethabi"
|
||||
version = "14.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01317735d563b3bad2d5f90d2e1799f414165408251abb762510f40e790e69a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ethereum-types",
|
||||
"hex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha3",
|
||||
"thiserror",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethbloom"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb684ac8fa8f6c5759f788862bb22ec6fe3cb392f6bfd08e3c64b603661e3f8"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
"fixed-hash",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethereum-types"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f64b5df66a228d85e4b17e5d6c6aa43b0310898ffe8a85988c4c032357aaabfd"
|
||||
dependencies = [
|
||||
"ethbloom",
|
||||
"fixed-hash",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"primitive-types",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "0.1.0"
|
||||
@@ -1816,31 +1693,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bytemuck",
|
||||
"half",
|
||||
"serde",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-hash"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rand 0.8.4",
|
||||
"rustc-hex",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.22"
|
||||
@@ -1937,12 +1789,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.4"
|
||||
@@ -2052,12 +1898,6 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.17"
|
||||
@@ -2093,27 +1933,20 @@ name = "gateway-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"coconut-interface",
|
||||
"credentials",
|
||||
"crypto",
|
||||
"fluvio-wasm-timer",
|
||||
"futures",
|
||||
"gateway-requests",
|
||||
"getrandom 0.2.3",
|
||||
"json",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"rand 0.7.3",
|
||||
"secp256k1",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tungstenite",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-utils",
|
||||
"web3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2307,19 +2140,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.13.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"libgit2-sys",
|
||||
"log",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.14.5"
|
||||
@@ -2504,9 +2324,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
@@ -2589,12 +2409,6 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-literal"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b"
|
||||
|
||||
[[package]]
|
||||
name = "hkd32"
|
||||
version = "0.6.0"
|
||||
@@ -2826,44 +2640,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-codec"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-rlp"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808"
|
||||
dependencies = [
|
||||
"rlp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-serde"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b47ca4d2b6931707a55fce5cf66aff80e2178c8b63bbb4ecb5695cbc870ddf6f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-trait-for-tuples"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5dacb10c5b3bb92d46ba347505a9041e676bb20ad220101326bffb0c93031ee"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
@@ -3022,27 +2798,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpc-core"
|
||||
version = "18.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.9.6"
|
||||
@@ -3092,18 +2847,6 @@ version = "0.2.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.12.26+1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.1"
|
||||
@@ -3121,18 +2864,6 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lioness"
|
||||
version = "0.1.2"
|
||||
@@ -3312,15 +3043,10 @@ dependencies = [
|
||||
name = "mixnet-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"az",
|
||||
"cosmwasm-std",
|
||||
"fixed",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
@@ -3438,7 +3164,6 @@ checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
|
||||
name = "network-defaults"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal",
|
||||
"serde",
|
||||
"time 0.3.3",
|
||||
"url",
|
||||
@@ -3573,7 +3298,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "0.12.1"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"client-core",
|
||||
@@ -3587,7 +3312,6 @@ dependencies = [
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"pemstore",
|
||||
"pretty_env_logger",
|
||||
@@ -3600,14 +3324,13 @@ dependencies = [
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vergen",
|
||||
"version-checker",
|
||||
"websocket-requests",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-client-wasm"
|
||||
version = "0.12.0"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"coconut-interface",
|
||||
"console_error_panic_hook",
|
||||
@@ -3631,14 +3354,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "0.12.1"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"bandwidth-claim-contract",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"clap",
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"config",
|
||||
"credentials",
|
||||
"crypto",
|
||||
@@ -3646,20 +3365,17 @@ dependencies = [
|
||||
"dirs",
|
||||
"dotenv",
|
||||
"futures",
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
"humantime-serde",
|
||||
"log",
|
||||
"mixnet-client",
|
||||
"mixnode-common",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"pemstore",
|
||||
"pretty_env_logger",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"subtle-encoding",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -3667,14 +3383,12 @@ dependencies = [
|
||||
"tokio-util",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vergen",
|
||||
"version-checker",
|
||||
"web3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "0.12.1"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -3702,13 +3416,12 @@ dependencies = [
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vergen",
|
||||
"version-checker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "0.12.0"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"dirs",
|
||||
@@ -3729,7 +3442,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "0.12.1"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"client-core",
|
||||
@@ -3743,7 +3456,6 @@ dependencies = [
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"ordered-buffer",
|
||||
"pemstore",
|
||||
@@ -3758,13 +3470,12 @@ dependencies = [
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vergen",
|
||||
"version-checker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator-api"
|
||||
version = "0.12.0"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -3784,8 +3495,6 @@ dependencies = [
|
||||
"pin-project",
|
||||
"pretty_env_logger",
|
||||
"rand 0.7.3",
|
||||
"rand 0.8.4",
|
||||
"rand_chacha 0.3.1",
|
||||
"reqwest",
|
||||
"rocket",
|
||||
"rocket_cors",
|
||||
@@ -3799,7 +3508,6 @@ dependencies = [
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vergen",
|
||||
"version-checker",
|
||||
]
|
||||
|
||||
@@ -3973,11 +3681,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "okapi"
|
||||
version = "0.7.0-rc.1"
|
||||
version = "0.6.0-alpha-1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce66b6366e049880a35c378123fddb630b1a1a3c37fa1ca70caaf4a09f6e2893"
|
||||
checksum = "bb085e00daf8d75b9dbf0ffdb4738e69503e28898d9641fa8bdc6ad536c7bcf4"
|
||||
dependencies = [
|
||||
"log",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4090,32 +3797,6 @@ dependencies = [
|
||||
"system-deps 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.1",
|
||||
"bitvec",
|
||||
"byte-slice-cast",
|
||||
"impl-trait-for-tuples",
|
||||
"parity-scale-codec-derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec-derive"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.1.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.0.0"
|
||||
@@ -4510,19 +4191,6 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primitive-types"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06345ee39fbccfb06ab45f3a1a5798d9dafa04cb8921a76d227040003a234b0e"
|
||||
dependencies = [
|
||||
"fixed-hash",
|
||||
"impl-codec",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "0.1.5"
|
||||
@@ -4693,12 +4361,6 @@ dependencies = [
|
||||
"scheduled-thread-pool",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.6.5"
|
||||
@@ -5104,16 +4766,6 @@ dependencies = [
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rlp"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"rustc-hex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocket"
|
||||
version = "0.5.0-rc.1"
|
||||
@@ -5214,12 +4866,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rocket_okapi"
|
||||
version = "0.8.0-rc.1"
|
||||
version = "0.7.0-alpha-1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0025aa04994af8cd8e1fcdd5a73579a395c941ae090ecb0a39b41cca7e237a20"
|
||||
checksum = "8b2f4f48fb070f9f6c56d5663df5fa8a514406207744f4abd84661bfb24efd7d"
|
||||
dependencies = [
|
||||
"either",
|
||||
"log",
|
||||
"okapi",
|
||||
"rocket",
|
||||
"rocket_okapi_codegen",
|
||||
@@ -5230,9 +4880,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rocket_okapi_codegen"
|
||||
version = "0.8.0-rc.1"
|
||||
version = "0.7.0-alpha-1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc114779fc27afb78179233e966f469e47fd7a98dc15181cff2574cdddb65612"
|
||||
checksum = "88ccf1550e1c806461a6b08e2ab64eb10701d41bf50bde59ab9aa3a57ab14d41"
|
||||
dependencies = [
|
||||
"darling 0.13.0",
|
||||
"proc-macro2",
|
||||
@@ -5264,12 +4914,6 @@ dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hex"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
@@ -5409,24 +5053,6 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a"
|
||||
dependencies = [
|
||||
"secp256k1-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "827cb7cce42533829c792fc51b82fbf18b125b45a702ef2c8be77fce65463a7b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.4.2"
|
||||
@@ -5803,21 +5429,6 @@ dependencies = [
|
||||
"ordered-buffer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soketto"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4919971d141dbadaa0e82b5d369e2d7666c98e4625046140615ca363e50d4daa"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"futures",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.4",
|
||||
"sha-1 0.9.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soup-sys"
|
||||
version = "0.10.0"
|
||||
@@ -6417,12 +6028,6 @@ dependencies = [
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.37"
|
||||
@@ -6815,15 +6420,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
@@ -6931,7 +6527,6 @@ checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
@@ -6996,9 +6591,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "5.1.1"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f34750e8cbb4d87d09f6d7441921cc6d9435dcc58df53bbaec01f5f9945d4801"
|
||||
checksum = "369e48de67506679b3a576b0faf666fa9f9acf2fd00b4c61e28bdb6c8e08ec06"
|
||||
dependencies = [
|
||||
"dprint-plugin-typescript",
|
||||
"ts-rs-macros",
|
||||
@@ -7006,9 +6601,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "5.1.0"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c78e4905e64bb23c814098885e08778dfed7fd796ea503df29aa3ba5fead009"
|
||||
checksum = "f269e8fd28e26b4cdbd01f81f345aaf666131511e54a735a76a614b5062d0a5a"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
@@ -7211,7 +6806,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
"ts-rs",
|
||||
"url",
|
||||
"vesting-contract",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7226,23 +6820,6 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
version = "5.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if 1.0.0",
|
||||
"chrono",
|
||||
"enum-iterator",
|
||||
"getset",
|
||||
"git2",
|
||||
"rustc_version 0.4.0",
|
||||
"rustversion",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version-checker"
|
||||
version = "0.1.0"
|
||||
@@ -7268,21 +6845,6 @@ version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "vesting-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"getrandom 0.2.3",
|
||||
"mixnet-contract",
|
||||
"rand 0.8.4",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.1.0"
|
||||
@@ -7436,52 +6998,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web3"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd24abe6f2b68e0677f843059faea87bcbd4892e39f02886f366d8222c3c540d"
|
||||
dependencies = [
|
||||
"arrayvec 0.5.2",
|
||||
"base64",
|
||||
"bytes",
|
||||
"derive_more",
|
||||
"ethabi",
|
||||
"ethereum-types",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"headers",
|
||||
"hex",
|
||||
"jsonrpc-core",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"pin-project",
|
||||
"reqwest",
|
||||
"rlp",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"soketto",
|
||||
"tiny-keccak",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"url",
|
||||
"web3-async-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web3-async-native-tls"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk"
|
||||
version = "0.14.0"
|
||||
@@ -7692,12 +7208,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.19.1"
|
||||
|
||||
+1
-3
@@ -25,7 +25,6 @@ members = [
|
||||
"common/config",
|
||||
"common/credentials",
|
||||
"common/crypto",
|
||||
"common/bandwidth-claim-contract",
|
||||
"common/mixnet-contract",
|
||||
"common/mixnode-common",
|
||||
"common/network-defaults",
|
||||
@@ -62,7 +61,6 @@ default-members = [
|
||||
"service-providers/network-requester",
|
||||
"mixnode",
|
||||
"validator-api",
|
||||
"explorer-api",
|
||||
]
|
||||
|
||||
exclude = ["explorer", "contracts", "tokenomics-py"]
|
||||
exclude = ["explorer", "contracts"]
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
all: clippy-all test fmt
|
||||
happy: clippy-happy test fmt
|
||||
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet
|
||||
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet
|
||||
test: test-main test-contracts test-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
|
||||
|
||||
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
|
||||
@@ -5,6 +5,8 @@ SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
## The Nym Privacy Platform
|
||||
|
||||
This repository contains the Nym mixnet.
|
||||
|
||||
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.
|
||||
@@ -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-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
|
||||
* nym-explorer - a (projected) block explorer and (existing) mixnet viewer.
|
||||
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
|
||||
* nym-wallet (currently in development)- a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
|
||||
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
|
||||
@@ -31,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.
|
||||
|
||||
### 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
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "0.12.0"
|
||||
version = "0.11.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -30,6 +30,3 @@ validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
coconut = []
|
||||
@@ -13,6 +13,7 @@ use nymsphinx::utils::sample_poisson_duration;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time;
|
||||
|
||||
@@ -164,8 +165,8 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
self.run().await;
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use futures::StreamExt;
|
||||
use gateway_client::GatewayClient;
|
||||
use log::*;
|
||||
use nymsphinx::forwarding::packet::MixPacket;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
|
||||
@@ -71,8 +72,8 @@ impl MixTrafficController {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
self.run().await;
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use nymsphinx::addressing::clients::Recipient;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
mod acknowledgement_control;
|
||||
@@ -169,8 +170,10 @@ impl RealMessagesController<OsRng> {
|
||||
self.ack_control = Some(ack_control_fut.await.unwrap());
|
||||
}
|
||||
|
||||
pub fn start(mut self) -> JoinHandle<Self> {
|
||||
tokio::spawn(async move {
|
||||
// &Handle is only passed for consistency sake with other client modules, but I think
|
||||
// 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
|
||||
})
|
||||
|
||||
@@ -15,6 +15,7 @@ use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorith
|
||||
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
|
||||
@@ -290,8 +291,8 @@ impl RequestReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
while let Some(request) = self.query_receiver.next().await {
|
||||
match request {
|
||||
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
|
||||
@@ -321,8 +322,8 @@ impl FragmentedMessageReceiver {
|
||||
mixnet_packet_receiver,
|
||||
}
|
||||
}
|
||||
fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
while let Some(new_messages) = self.mixnet_packet_receiver.next().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?
|
||||
self.fragmented_message_receiver.start();
|
||||
self.request_receiver.start();
|
||||
self.fragmented_message_receiver.start(handle);
|
||||
self.request_receiver.start(handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use tokio::task::JoinHandle;
|
||||
use topology::{nym_topology_from_bonds, NymTopology};
|
||||
@@ -303,8 +304,8 @@ impl TopologyRefresher {
|
||||
self.topology_accessor.is_routable().await
|
||||
}
|
||||
|
||||
pub fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
|
||||
handle.spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(self.refresh_rate).await;
|
||||
self.refresh().await;
|
||||
|
||||
@@ -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_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
|
||||
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
|
||||
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
|
||||
// bandwidth bridging protocol, we can come back to a smaller timeout value
|
||||
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
||||
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
|
||||
pub fn missing_string_value() -> String {
|
||||
MISSING_VALUE.to_string()
|
||||
@@ -103,24 +100,9 @@ impl<T: NymConfig> Config<T> {
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn with_testnet_mode(&mut self, testnet_mode: bool) {
|
||||
self.client.testnet_mode = testnet_mode;
|
||||
}
|
||||
|
||||
pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) {
|
||||
self.client.gateway_id = id.into();
|
||||
}
|
||||
@@ -129,16 +111,6 @@ impl<T: NymConfig> Config<T> {
|
||||
self.client.gateway_listener = gateway_listener.into();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub fn with_eth_private_key<S: Into<String>>(&mut self, eth_private_key: S) {
|
||||
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>) {
|
||||
self.client.validator_api_urls = validator_api_urls;
|
||||
}
|
||||
@@ -157,10 +129,6 @@ impl<T: NymConfig> Config<T> {
|
||||
self.client.id.clone()
|
||||
}
|
||||
|
||||
pub fn get_testnet_mode(&self) -> bool {
|
||||
self.client.testnet_mode
|
||||
}
|
||||
|
||||
pub fn get_nym_root_directory(&self) -> PathBuf {
|
||||
self.client.nym_root_directory.clone()
|
||||
}
|
||||
@@ -205,21 +173,6 @@ impl<T: NymConfig> Config<T> {
|
||||
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
|
||||
pub fn get_average_packet_delay(&self) -> Duration {
|
||||
self.debug.average_packet_delay
|
||||
@@ -281,11 +234,6 @@ pub struct Client<T> {
|
||||
/// ID specifies the human readable ID of this particular client.
|
||||
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.
|
||||
validator_api_urls: Vec<Url>,
|
||||
|
||||
@@ -320,20 +268,6 @@ pub struct Client<T> {
|
||||
/// Address of the gateway listener to which all client requests should be sent.
|
||||
gateway_listener: String,
|
||||
|
||||
/// 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.
|
||||
#[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.
|
||||
/// It is expected to use default value and hence .toml file should not redefine this field.
|
||||
nym_root_directory: PathBuf,
|
||||
@@ -348,7 +282,6 @@ impl<T: NymConfig> Default for Client<T> {
|
||||
Client {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
id: "".to_string(),
|
||||
testnet_mode: false,
|
||||
validator_api_urls: default_api_endpoints(),
|
||||
private_identity_key_file: Default::default(),
|
||||
public_identity_key_file: Default::default(),
|
||||
@@ -359,12 +292,6 @@ impl<T: NymConfig> Default for Client<T> {
|
||||
reply_encryption_key_store_path: Default::default(),
|
||||
gateway_id: "".to_string(),
|
||||
gateway_listener: "".to_string(),
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
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(),
|
||||
super_struct: Default::default(),
|
||||
}
|
||||
@@ -399,11 +326,6 @@ impl<T: NymConfig> Client<T> {
|
||||
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
|
||||
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)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
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>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
@@ -24,7 +24,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)
|
||||
log = "0.4" # self explanatory
|
||||
pretty_env_logger = "0.4" # for formatting log messages
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
|
||||
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
|
||||
sled = "0.34" # for storage of replySURB decryption keys
|
||||
tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal"] } # async runtime
|
||||
@@ -44,14 +44,9 @@ topology = { path = "../../common/topology" }
|
||||
websocket-requests = { path = "websocket-requests" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
eth = []
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0" # for the "textsend" example
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
@@ -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
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcutil v1.0.2 // indirect
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ pub(crate) fn config_template() -> &'static str {
|
||||
// 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
|
||||
// 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#"
|
||||
# This is a TOML config file.
|
||||
# For more information, see https://github.com/toml-lang/toml
|
||||
@@ -19,10 +19,6 @@ version = '{{ client.version }}'
|
||||
# Human readable ID of this particular client.
|
||||
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.
|
||||
validator_api_urls = [
|
||||
{{#each client.validator_api_urls }}
|
||||
@@ -46,17 +42,6 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
|
||||
# sent but not received back.
|
||||
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 #####
|
||||
|
||||
# ID of the gateway from which the client should be fetching messages.
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use client_core::client::inbound_messages::{
|
||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||
@@ -20,14 +24,19 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::{hash_to_scalar, Credential, Parameters};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::bandwidth::{
|
||||
prepare_for_spending, BandwidthVoucherAttributes, BANDWIDTH_VALUE, TOTAL_ATTRIBUTES,
|
||||
};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
use gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
MixnetMessageSender,
|
||||
};
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
@@ -43,6 +52,11 @@ pub struct NymClient {
|
||||
/// key filepaths, etc.
|
||||
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.
|
||||
key_manager: KeyManager,
|
||||
|
||||
@@ -62,6 +76,7 @@ impl NymClient {
|
||||
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
|
||||
|
||||
NymClient {
|
||||
runtime: Runtime::new().unwrap(),
|
||||
config,
|
||||
key_manager,
|
||||
input_tx: None,
|
||||
@@ -87,6 +102,9 @@ impl NymClient {
|
||||
mix_tx: BatchMixMessageSender,
|
||||
) {
|
||||
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(
|
||||
self.key_manager.ack_key(),
|
||||
@@ -99,7 +117,7 @@ impl NymClient {
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
)
|
||||
.start();
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
fn start_real_traffic_controller(
|
||||
@@ -121,6 +139,10 @@ impl NymClient {
|
||||
);
|
||||
|
||||
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(
|
||||
controller_config,
|
||||
@@ -130,7 +152,7 @@ impl NymClient {
|
||||
topology_accessor,
|
||||
reply_key_storage,
|
||||
)
|
||||
.start();
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -148,10 +170,45 @@ impl NymClient {
|
||||
mixnet_receiver,
|
||||
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 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 = credentials::bandwidth::obtain_signature(
|
||||
¶ms,
|
||||
&bandwidth_credential_attributes,
|
||||
&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,
|
||||
&bandwidth_credential_attributes,
|
||||
&verification_key,
|
||||
)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
fn start_gateway_client(
|
||||
&mut self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
@@ -168,44 +225,35 @@ impl NymClient {
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.expect("provided gateway id is invalid!");
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
*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");
|
||||
self.runtime.block_on(async {
|
||||
#[cfg(feature = "coconut")]
|
||||
let coconut_credential = self.prepare_coconut_credential().await;
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
Some(self.key_manager.gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
Some(bandwidth_controller),
|
||||
);
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
Some(self.key_manager.gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
);
|
||||
|
||||
if self.config.get_base().get_testnet_mode() {
|
||||
gateway_client.set_testnet_mode(true)
|
||||
}
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
gateway_client
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
Some(coconut_credential),
|
||||
)
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
gateway_client
|
||||
gateway_client
|
||||
})
|
||||
}
|
||||
|
||||
// future responsible for periodically polling directory server and updating
|
||||
// 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(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
self.config.get_base().get_topology_refresh_rate(),
|
||||
@@ -216,10 +264,13 @@ impl NymClient {
|
||||
// before returning, block entire runtime to refresh the current network view so that any
|
||||
// components depending on topology would see a non-empty view
|
||||
info!("Obtaining initial network topology");
|
||||
topology_refresher.refresh().await;
|
||||
self.runtime.block_on(topology_refresher.refresh());
|
||||
|
||||
// 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!(
|
||||
"The current network topology seem to be insufficient to route any packets through\
|
||||
- check if enough nodes and a gateway are online"
|
||||
@@ -227,7 +278,7 @@ impl NymClient {
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -240,7 +291,7 @@ impl NymClient {
|
||||
gateway_client: GatewayClient,
|
||||
) {
|
||||
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(
|
||||
@@ -253,7 +304,8 @@ impl NymClient {
|
||||
let websocket_handler =
|
||||
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
|
||||
@@ -300,9 +352,9 @@ impl NymClient {
|
||||
}
|
||||
|
||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||
pub async fn run_forever(&mut self) {
|
||||
self.start().await;
|
||||
if let Err(e) = tokio::signal::ctrl_c().await {
|
||||
pub fn run_forever(&mut self) {
|
||||
self.start();
|
||||
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
|
||||
error!(
|
||||
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
|
||||
e
|
||||
@@ -314,7 +366,7 @@ impl NymClient {
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) {
|
||||
pub fn start(&mut self) {
|
||||
info!("Starting nym client");
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
@@ -346,17 +398,14 @@ impl NymClient {
|
||||
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone())
|
||||
.await;
|
||||
self.start_topology_refresher(shared_topology_accessor.clone());
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
reply_key_storage.clone(),
|
||||
);
|
||||
|
||||
let gateway_client = self
|
||||
.start_gateway_client(mixnet_messages_sender, ack_sender)
|
||||
.await;
|
||||
let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
|
||||
|
||||
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
|
||||
self.start_real_traffic_controller(
|
||||
|
||||
@@ -1,45 +1,39 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use rand::rngs::OsRng;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use url::Url;
|
||||
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::{hash_to_scalar, Credential, Parameters};
|
||||
use config::NymConfig;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::coconut::bandwidth::{
|
||||
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
|
||||
use credentials::bandwidth::{
|
||||
prepare_for_spending, BandwidthVoucherAttributes, BANDWIDTH_VALUE, TOTAL_ATTRIBUTES,
|
||||
};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
#[cfg(feature = "coconut")]
|
||||
use network_defaults::BANDWIDTH_VALUE;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use topology::{filter::VersionFilterable, gateway};
|
||||
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> {
|
||||
let app = App::new("init")
|
||||
App::new("init")
|
||||
.about("Initialise a Nym client. Do this first!")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
@@ -71,31 +65,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.long("fastmode")
|
||||
.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")
|
||||
);
|
||||
#[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
|
||||
@@ -114,10 +84,13 @@ async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8])
|
||||
voucher_info: hash_to_scalar(String::from("BandwidthVoucher").as_bytes()),
|
||||
};
|
||||
|
||||
let bandwidth_credential =
|
||||
obtain_signature(¶ms, &bandwidth_credential_attributes, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
let bandwidth_credential = credentials::bandwidth::obtain_signature(
|
||||
¶ms,
|
||||
&bandwidth_credential_attributes,
|
||||
validators,
|
||||
)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
prepare_for_spending(
|
||||
raw_identity,
|
||||
@@ -219,7 +192,7 @@ fn show_address(config: &Config) {
|
||||
println!("\nThe address of this client is: {}", client_recipient);
|
||||
}
|
||||
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = matches.value_of("id").unwrap(); // required for now
|
||||
@@ -237,7 +210,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
|
||||
// TODO: ideally that should be the last thing that's being done to config.
|
||||
// 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") {
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
@@ -250,20 +223,26 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
|
||||
let chosen_gateway_id = matches.value_of("gateway");
|
||||
|
||||
let gateway_details = gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_id(gateway_details.identity_key.to_base58_string());
|
||||
let shared_keys =
|
||||
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
|
||||
let registration_fut = async {
|
||||
let gate_details = gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
chosen_gateway_id,
|
||||
)
|
||||
.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())
|
||||
};
|
||||
|
||||
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_listener(gateway_details.clients_address());
|
||||
.with_gateway_listener(gateway_listener);
|
||||
key_manager.insert_gateway_shared_key(shared_keys);
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
|
||||
|
||||
@@ -5,18 +5,6 @@ use crate::client::config::{Config, SocketType};
|
||||
use clap::ArgMatches;
|
||||
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 run;
|
||||
pub(crate) mod upgrade;
|
||||
@@ -55,26 +43,5 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -4,16 +4,13 @@
|
||||
use crate::client::config::Config;
|
||||
use crate::client::NymClient;
|
||||
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 config::NymConfig;
|
||||
use log::*;
|
||||
use version_checker::is_minor_version_compatible;
|
||||
|
||||
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")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
@@ -41,26 +38,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.long("port")
|
||||
.help("Port for the socket (if applicable) to listen on")
|
||||
.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
|
||||
@@ -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 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) {
|
||||
error!("failed the local version check");
|
||||
return;
|
||||
}
|
||||
|
||||
NymClient::new(config).run_forever().await;
|
||||
NymClient::new(config).run_forever();
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, App, ArgMatches};
|
||||
use clap::{App, ArgMatches};
|
||||
|
||||
pub mod client;
|
||||
pub mod commands;
|
||||
pub mod websocket;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let arg_matches = App::new("Nym Client")
|
||||
.version(crate_version!())
|
||||
.long_version(&*long_version())
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Nymtech")
|
||||
.about("Implementation of the Nym Client")
|
||||
.subcommand(commands::init::command_args())
|
||||
@@ -23,13 +21,13 @@ async fn main() {
|
||||
.subcommand(commands::upgrade::command_args())
|
||||
.get_matches();
|
||||
|
||||
execute(arg_matches).await;
|
||||
execute(arg_matches);
|
||||
}
|
||||
|
||||
async fn execute(matches: ArgMatches<'static>) {
|
||||
fn execute(matches: ArgMatches) {
|
||||
match matches.subcommand() {
|
||||
("init", Some(m)) => commands::init::execute(m.clone()).await,
|
||||
("run", Some(m)) => commands::run::execute(m.clone()).await,
|
||||
("init", Some(m)) => commands::init::execute(m),
|
||||
("run", Some(m)) => commands::run::execute(m),
|
||||
("upgrade", Some(m)) => commands::upgrade::execute(m),
|
||||
_ => println!("{}", usage()),
|
||||
}
|
||||
@@ -52,38 +50,7 @@ fn banner() -> String {
|
||||
(client - version {:})
|
||||
|
||||
"#,
|
||||
crate_version!()
|
||||
)
|
||||
}
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
"Build Version:",
|
||||
env!("VERGEN_BUILD_SEMVER"),
|
||||
"Commit SHA:",
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
"Commit Date:",
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
"Commit Branch:",
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
"rustc Version:",
|
||||
env!("VERGEN_RUSTC_SEMVER"),
|
||||
"rustc Channel:",
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
env!("CARGO_PKG_VERSION")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use super::handler::Handler;
|
||||
use log::*;
|
||||
use std::{net::SocketAddr, process, sync::Arc};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::runtime;
|
||||
use tokio::{sync::Notify, task::JoinHandle};
|
||||
|
||||
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());
|
||||
|
||||
tokio::spawn(async move { self.run(handler).await })
|
||||
rt_handle.spawn(async move { self.run(handler).await })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) use handler::Handler;
|
||||
pub(crate) use listener::Listener;
|
||||
|
||||
pub(crate) mod handler;
|
||||
pub(crate) mod listener;
|
||||
|
||||
pub(crate) use handler::Handler;
|
||||
pub(crate) use listener::Listener;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "0.12.1"
|
||||
version = "0.11.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
@@ -32,18 +32,13 @@ crypto = { path = "../../common/crypto" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
nymsphinx = { path = "../../common/nymsphinx" }
|
||||
ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
|
||||
ordered-buffer = {path = "../../common/socks5/ordered-buffer"}
|
||||
socks5-requests = { path = "../../common/socks5/requests" }
|
||||
topology = { path = "../../common/topology" }
|
||||
pemstore = { path = "../../common/pemstore" }
|
||||
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
eth = []
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
@@ -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")
|
||||
}
|
||||
@@ -5,7 +5,7 @@ pub(crate) fn config_template() -> &'static str {
|
||||
// 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
|
||||
// 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#"
|
||||
# This is a TOML config file.
|
||||
# For more information, see https://github.com/toml-lang/toml
|
||||
@@ -19,10 +19,6 @@ version = '{{ client.version }}'
|
||||
# Human readable ID of this particular client.
|
||||
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.
|
||||
validator_api_urls = [
|
||||
{{#each client.validator_api_urls }}
|
||||
@@ -46,17 +42,6 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
|
||||
# sent but not received back.
|
||||
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 #####
|
||||
|
||||
# ID of the gateway from which the client should be fetching messages.
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use client_core::client::inbound_messages::{
|
||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||
@@ -18,14 +22,19 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::{hash_to_scalar, Credential, Parameters};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::bandwidth::{
|
||||
prepare_for_spending, BandwidthVoucherAttributes, BANDWIDTH_VALUE, TOTAL_ATTRIBUTES,
|
||||
};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
use gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
MixnetMessageSender,
|
||||
};
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
|
||||
@@ -42,6 +51,11 @@ pub struct NymClient {
|
||||
/// key filepaths, etc.
|
||||
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.
|
||||
key_manager: KeyManager,
|
||||
}
|
||||
@@ -52,6 +66,7 @@ impl NymClient {
|
||||
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
|
||||
|
||||
NymClient {
|
||||
runtime: Runtime::new().unwrap(),
|
||||
config,
|
||||
key_manager,
|
||||
}
|
||||
@@ -75,6 +90,9 @@ impl NymClient {
|
||||
mix_tx: BatchMixMessageSender,
|
||||
) {
|
||||
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(
|
||||
self.key_manager.ack_key(),
|
||||
@@ -87,7 +105,7 @@ impl NymClient {
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
)
|
||||
.start();
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
fn start_real_traffic_controller(
|
||||
@@ -109,6 +127,10 @@ impl NymClient {
|
||||
);
|
||||
|
||||
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(
|
||||
controller_config,
|
||||
@@ -118,7 +140,7 @@ impl NymClient {
|
||||
topology_accessor,
|
||||
reply_key_storage,
|
||||
)
|
||||
.start();
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -136,10 +158,45 @@ impl NymClient {
|
||||
mixnet_receiver,
|
||||
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 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 = credentials::bandwidth::obtain_signature(
|
||||
¶ms,
|
||||
&bandwidth_credential_attributes,
|
||||
&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,
|
||||
&bandwidth_credential_attributes,
|
||||
&verification_key,
|
||||
)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
fn start_gateway_client(
|
||||
&mut self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
@@ -156,44 +213,35 @@ impl NymClient {
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.expect("provided gateway id is invalid!");
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
let bandwidth_controller = BandwidthController::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
*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");
|
||||
self.runtime.block_on(async {
|
||||
#[cfg(feature = "coconut")]
|
||||
let coconut_credential = self.prepare_coconut_credential().await;
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
Some(self.key_manager.gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
Some(bandwidth_controller),
|
||||
);
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
Some(self.key_manager.gateway_shared_key()),
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.get_base().get_gateway_response_timeout(),
|
||||
);
|
||||
|
||||
if self.config.get_base().get_testnet_mode() {
|
||||
gateway_client.set_testnet_mode(true)
|
||||
}
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
gateway_client
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
Some(coconut_credential),
|
||||
)
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
gateway_client
|
||||
gateway_client
|
||||
})
|
||||
}
|
||||
|
||||
// future responsible for periodically polling directory server and updating
|
||||
// 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(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
self.config.get_base().get_topology_refresh_rate(),
|
||||
@@ -204,10 +252,13 @@ impl NymClient {
|
||||
// before returning, block entire runtime to refresh the current network view so that any
|
||||
// components depending on topology would see a non-empty view
|
||||
info!("Obtaining initial network topology");
|
||||
topology_refresher.refresh().await;
|
||||
self.runtime.block_on(topology_refresher.refresh());
|
||||
|
||||
// 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!(
|
||||
"The current network topology seem to be insufficient to route any packets through\
|
||||
- check if enough nodes and a gateway are online"
|
||||
@@ -215,7 +266,7 @@ impl NymClient {
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -228,7 +279,7 @@ impl NymClient {
|
||||
gateway_client: GatewayClient,
|
||||
) {
|
||||
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(
|
||||
@@ -247,13 +298,14 @@ impl NymClient {
|
||||
self.config.get_provider_mix_address(),
|
||||
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)
|
||||
pub async fn run_forever(&mut self) {
|
||||
self.start().await;
|
||||
if let Err(e) = tokio::signal::ctrl_c().await {
|
||||
pub fn run_forever(&mut self) {
|
||||
self.start();
|
||||
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
|
||||
error!(
|
||||
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
|
||||
e
|
||||
@@ -265,7 +317,7 @@ impl NymClient {
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) {
|
||||
pub fn start(&mut self) {
|
||||
info!("Starting nym client");
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
@@ -297,17 +349,14 @@ impl NymClient {
|
||||
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone())
|
||||
.await;
|
||||
self.start_topology_refresher(shared_topology_accessor.clone());
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
reply_key_storage.clone(),
|
||||
);
|
||||
|
||||
let gateway_client = self
|
||||
.start_gateway_client(mixnet_messages_sender, ack_sender)
|
||||
.await;
|
||||
let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
|
||||
|
||||
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
|
||||
self.start_real_traffic_controller(
|
||||
|
||||
@@ -1,43 +1,37 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use rand::{prelude::SliceRandom, rngs::OsRng, thread_rng};
|
||||
use url::Url;
|
||||
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::{hash_to_scalar, Credential, Parameters};
|
||||
use config::NymConfig;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::coconut::bandwidth::{
|
||||
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
|
||||
use credentials::bandwidth::{
|
||||
prepare_for_spending, BandwidthVoucherAttributes, BANDWIDTH_VALUE, TOTAL_ATTRIBUTES,
|
||||
};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::obtain_aggregate_verification_key;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
#[cfg(feature = "coconut")]
|
||||
use network_defaults::BANDWIDTH_VALUE;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use rand::{prelude::SliceRandom, rngs::OsRng, thread_rng};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use topology::{filter::VersionFilterable, gateway};
|
||||
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> {
|
||||
let app = App::new("init")
|
||||
App::new("init")
|
||||
.about("Initialise a Nym client. Do this first!")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
@@ -71,31 +65,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.long("fastmode")
|
||||
.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")
|
||||
);
|
||||
#[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
|
||||
@@ -114,10 +84,13 @@ async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8])
|
||||
voucher_info: hash_to_scalar("BandwidthVoucher"),
|
||||
};
|
||||
|
||||
let bandwidth_credential =
|
||||
obtain_signature(¶ms, &bandwidth_credential_attributes, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
let bandwidth_credential = credentials::bandwidth::obtain_signature(
|
||||
¶ms,
|
||||
&bandwidth_credential_attributes,
|
||||
validators,
|
||||
)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
prepare_for_spending(
|
||||
raw_identity,
|
||||
@@ -219,7 +192,7 @@ fn show_address(config: &Config) {
|
||||
println!("\nThe address of this client is: {}", client_recipient);
|
||||
}
|
||||
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = matches.value_of("id").unwrap(); // required for now
|
||||
@@ -238,7 +211,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
|
||||
// TODO: ideally that should be the last thing that's being done to config.
|
||||
// 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") {
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
@@ -251,20 +224,26 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
|
||||
let chosen_gateway_id = matches.value_of("gateway");
|
||||
|
||||
let gateway_details = gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_id(gateway_details.identity_key.to_base58_string());
|
||||
let shared_keys =
|
||||
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
|
||||
let registration_fut = async {
|
||||
let gate_details = gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
chosen_gateway_id,
|
||||
)
|
||||
.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())
|
||||
};
|
||||
|
||||
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_gateway_listener(gateway_details.clients_address());
|
||||
.with_gateway_listener(gateway_listener);
|
||||
key_manager.insert_gateway_shared_key(shared_keys);
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
|
||||
|
||||
@@ -9,18 +9,6 @@ pub(crate) mod init;
|
||||
pub(crate) mod run;
|
||||
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> {
|
||||
raw.split(',')
|
||||
.map(|raw_validator| {
|
||||
@@ -51,26 +39,5 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -4,16 +4,13 @@
|
||||
use crate::client::config::Config;
|
||||
use crate::client::NymClient;
|
||||
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 config::NymConfig;
|
||||
use log::*;
|
||||
use version_checker::is_minor_version_compatible;
|
||||
|
||||
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")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
@@ -47,26 +44,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.long("port")
|
||||
.help("Port for the socket to listen on")
|
||||
.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
|
||||
@@ -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 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) {
|
||||
error!("failed the local version check");
|
||||
return;
|
||||
}
|
||||
|
||||
NymClient::new(config).run_forever().await;
|
||||
NymClient::new(config).run_forever();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, App, ArgMatches};
|
||||
use clap::{App, ArgMatches};
|
||||
|
||||
pub mod client;
|
||||
mod commands;
|
||||
pub mod socks;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
@@ -16,20 +15,19 @@ async fn main() {
|
||||
let arg_matches = App::new("Nym Socks5 Proxy")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Nymtech")
|
||||
.long_version(&*long_version())
|
||||
.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::run::command_args())
|
||||
.subcommand(commands::upgrade::command_args())
|
||||
.get_matches();
|
||||
|
||||
execute(arg_matches).await;
|
||||
execute(arg_matches);
|
||||
}
|
||||
|
||||
async fn execute(matches: ArgMatches<'static>) {
|
||||
fn execute(matches: ArgMatches) {
|
||||
match matches.subcommand() {
|
||||
("init", Some(m)) => commands::init::execute(m.clone()).await,
|
||||
("run", Some(m)) => commands::run::execute(m.clone()).await,
|
||||
("init", Some(m)) => commands::init::execute(m),
|
||||
("run", Some(m)) => commands::run::execute(m),
|
||||
("upgrade", Some(m)) => commands::upgrade::execute(m),
|
||||
_ => println!("{}", usage()),
|
||||
}
|
||||
@@ -52,38 +50,7 @@ fn banner() -> String {
|
||||
(socks5 proxy - version {:})
|
||||
|
||||
"#,
|
||||
crate_version!()
|
||||
)
|
||||
}
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
"Build Version:",
|
||||
env!("VERGEN_BUILD_SEMVER"),
|
||||
"Commit SHA:",
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
"Commit Date:",
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
"Commit Branch:",
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
"rustc Version:",
|
||||
env!("VERGEN_RUSTC_SEMVER"),
|
||||
"rustc Channel:",
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
env!("CARGO_PKG_VERSION")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
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
|
||||
@@ -1,83 +1,41 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
"node": true,
|
||||
"mocha": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2019,
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"plugins": ["prettier", "mocha"],
|
||||
"extends": [
|
||||
"airbnb-base",
|
||||
"airbnb-typescript/base",
|
||||
"prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"import/prefer-default-export": "off",
|
||||
"import/no-extraneous-dependencies": [
|
||||
"no-console": "off",
|
||||
"linebreak-style": "off",
|
||||
"quotes": [
|
||||
"error",
|
||||
"double",
|
||||
{
|
||||
"devDependencies": [
|
||||
"**/*.test.[jt]s",
|
||||
"**/*.spec.[jt]s"
|
||||
]
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
],
|
||||
"import/extensions": [
|
||||
"keyword-spacing": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
{
|
||||
"ts": "never",
|
||||
"js": "never"
|
||||
"before": true
|
||||
}
|
||||
],
|
||||
"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": "^_" }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2
|
||||
}
|
||||
@@ -3,20 +3,26 @@ Nym Validator Client
|
||||
|
||||
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
|
||||
-------------
|
||||
|
||||
The tests will be separated into three categories: unit, integration and mock.
|
||||
|
||||
Currently the command to run all tests:
|
||||
|
||||
```
|
||||
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
|
||||
------------------------
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
setupFiles: ["dotenv/config"]
|
||||
};
|
||||
Generated
+3848
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"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",
|
||||
"repository": "https://github.com/nymtech/nym",
|
||||
"main": "./dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"test": "jest --coverage --verbose false",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"build": "tsc",
|
||||
"test": "ts-mocha tests/**/*.test.ts",
|
||||
"coverage": "nyc npm test",
|
||||
"lint": "eslint \"**/*.ts\"",
|
||||
"docs": "typedoc --out docs src/index.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
@@ -19,31 +20,25 @@
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@types/jest": "27.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.7.0",
|
||||
"@typescript-eslint/parser": "^5.7.0",
|
||||
"@types/chai": "^4.2.15",
|
||||
"@types/expect": "^24.3.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-config-airbnb": "^19.0.2",
|
||||
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-root-import": "^1.0.4",
|
||||
"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",
|
||||
"mocha": "^8.2.1",
|
||||
"moq.ts": "^7.2.0",
|
||||
"nyc": "^15.1.0",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"typedoc": "^0.20.27",
|
||||
"typescript": "^4.5.4"
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"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",
|
||||
"cosmjs-types": "^0.4.0",
|
||||
"dotenv": "^10.0.0"
|
||||
"@cosmjs/cosmwasm-stargate": "^0.25.5",
|
||||
"@cosmjs/stargate": "^0.25.5",
|
||||
"@cosmjs/math": "^0.25.5",
|
||||
"@cosmjs/proto-signing": "^0.25.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
15.0.1
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,73 @@
|
||||
import { Decimal } from '@cosmjs/math';
|
||||
import { Coin } from '@cosmjs/stargate';
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import { Coin } from ".";
|
||||
|
||||
// NARROW NO-BREAK SPACE (U+202F)
|
||||
const thinSpace = '\u202F';
|
||||
const thinSpace = "\u202F";
|
||||
|
||||
export function printableCoin(coin?: Coin): string {
|
||||
if (!coin) {
|
||||
return '0';
|
||||
}
|
||||
if (coin.denom.startsWith('u')) {
|
||||
const ticker = coin.denom.slice(1).toUpperCase();
|
||||
return Decimal.fromAtomics(coin.amount, 6).toString() + thinSpace + ticker;
|
||||
}
|
||||
return coin.amount + thinSpace + coin.denom;
|
||||
if (!coin) {
|
||||
return "0";
|
||||
}
|
||||
if (coin.denom.startsWith("u")) {
|
||||
const ticker = coin.denom.slice(1).toUpperCase();
|
||||
return Decimal.fromAtomics(coin.amount, 6).toString() + thinSpace + ticker;
|
||||
} else {
|
||||
return coin.amount + thinSpace + coin.denom;
|
||||
}
|
||||
}
|
||||
|
||||
export function printableBalance(balance?: readonly Coin[]): string {
|
||||
if (!balance || balance.length === 0) return '–';
|
||||
return balance.map(printableCoin).join(', ');
|
||||
if (!balance || balance.length === 0) return "–";
|
||||
return balance.map(printableCoin).join(", ");
|
||||
}
|
||||
|
||||
// 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"
|
||||
// Basically does the same job as `displayAmountToNative` but without the requirement
|
||||
// of having the coinMap
|
||||
export function printableBalanceToNative(amountToDisplay: string): string {
|
||||
const decimalAmount = Decimal.fromUserInput(amountToDisplay, 6);
|
||||
return decimalAmount.atomics;
|
||||
export function printableBalanceToNative(amountToDisplay: string): string {
|
||||
const decimalAmount = Decimal.fromUserInput(amountToDisplay, 6);
|
||||
return decimalAmount.atomics;
|
||||
}
|
||||
|
||||
// reciprocal of `printableBalanceToNative`, takes, for example 10000000 and returns 10
|
||||
export function nativeToPrintable(nativeValue: string): string {
|
||||
return Decimal.fromAtomics(nativeValue, 6).toString();
|
||||
return Decimal.fromAtomics(nativeValue, 6).toString()
|
||||
}
|
||||
|
||||
export interface MappedCoin {
|
||||
readonly denom: string;
|
||||
readonly fractionalDigits: number;
|
||||
readonly denom: string;
|
||||
readonly fractionalDigits: number;
|
||||
}
|
||||
|
||||
export interface CoinMap {
|
||||
readonly [key: string]: MappedCoin;
|
||||
readonly [key: string]: MappedCoin;
|
||||
}
|
||||
|
||||
export function nativeCoinToDisplay(coin: Coin, coinMap: CoinMap): Coin {
|
||||
if (!coinMap) return coin;
|
||||
if (!coinMap) return coin;
|
||||
|
||||
const coinToDisplay = coinMap[coin.denom];
|
||||
if (!coinToDisplay) return coin;
|
||||
const coinToDisplay = coinMap[coin.denom];
|
||||
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
|
||||
// with 6 fractional digits, this would be eg. "12034600"
|
||||
export function displayAmountToNative(amountToDisplay: string, coinMap: CoinMap, nativeDenom: string): string {
|
||||
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;
|
||||
}
|
||||
export function displayAmountToNative(
|
||||
amountToDisplay: string,
|
||||
coinMap: CoinMap,
|
||||
nativeDenom: string,
|
||||
): string {
|
||||
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;
|
||||
}
|
||||
|
||||
+623
-436
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,182 +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,
|
||||
RewardingIntervalResponse,
|
||||
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: {},
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
current_rewarding_interval: {},
|
||||
});
|
||||
}
|
||||
|
||||
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: {},
|
||||
});
|
||||
}
|
||||
|
||||
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_epoch_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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,218 +1,147 @@
|
||||
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
|
||||
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
|
||||
import { Coin } from "@cosmjs/stargate";
|
||||
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
|
||||
import {
|
||||
Account,
|
||||
Block,
|
||||
Coin,
|
||||
DeliverTxResponse,
|
||||
IndexedTx,
|
||||
SearchTxFilter,
|
||||
SearchTxQuery,
|
||||
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,
|
||||
RewardingIntervalResponse,
|
||||
RewardingStatus,
|
||||
} from './types';
|
||||
import ValidatorApiQuerier, { IValidatorApiQuery } from './validator-api-querier';
|
||||
Delegation,
|
||||
GatewayOwnershipResponse,
|
||||
MixOwnershipResponse, PagedGatewayDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse,
|
||||
PagedMixnodeResponse,
|
||||
StateParams
|
||||
} from "./types";
|
||||
|
||||
export interface ICosmWasmQuery {
|
||||
// methods exposed by `CosmWasmClient`
|
||||
getChainId(): Promise<string>;
|
||||
getHeight(): Promise<number>;
|
||||
getAccount(searchAddress: string): Promise<Account | null>;
|
||||
getSequence(address: string): Promise<SequenceResponse>;
|
||||
getBlock(height?: number): Promise<Block>;
|
||||
getBalance(address: string, searchDenom: string): Promise<Coin>;
|
||||
getTx(id: string): Promise<IndexedTx | null>;
|
||||
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
|
||||
disconnect(): void;
|
||||
broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise<DeliverTxResponse>;
|
||||
getCodes(): Promise<readonly Code[]>;
|
||||
getCodeDetails(codeId: number): Promise<CodeDetails>;
|
||||
getContracts(codeId: number): Promise<readonly string[]>;
|
||||
getContract(address: string): Promise<Contract>;
|
||||
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
|
||||
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
|
||||
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
|
||||
export interface IQueryClient {
|
||||
getBalance(address: string, stakeDenom: 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>;
|
||||
|
||||
changeValidator(newUrl: string): Promise<void>
|
||||
}
|
||||
|
||||
export interface INymdQuery {
|
||||
// nym-specific implemented inside NymQuerier
|
||||
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
|
||||
/**
|
||||
* 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 QueryClient implements IQueryClient {
|
||||
private cosmClient: CosmWasmClient;
|
||||
|
||||
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse>;
|
||||
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse>;
|
||||
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
|
||||
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
|
||||
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams>;
|
||||
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse>;
|
||||
private constructor(cosmClient: CosmWasmClient) {
|
||||
this.cosmClient = cosmClient;
|
||||
}
|
||||
|
||||
getAllNetworkDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
limit?: number,
|
||||
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>;
|
||||
public static async connect(url: string): Promise<IQueryClient> {
|
||||
const client = await CosmWasmClient.connect(url)
|
||||
return new QueryClient(client)
|
||||
}
|
||||
|
||||
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution>;
|
||||
getRewardPool(mixnetContractAddress: string): Promise<string>;
|
||||
getCirculatingSupply(mixnetContractAddress: string): Promise<string>;
|
||||
getEpochRewardPercent(mixnetContractAddress: string): Promise<number>;
|
||||
getSybilResistancePercent(mixnetContractAddress: string): Promise<number>;
|
||||
getRewardingStatus(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
rewardingIntervalNonce: number,
|
||||
): Promise<RewardingStatus>;
|
||||
}
|
||||
|
||||
export interface IQueryClient extends ICosmWasmQuery, INymdQuery, IValidatorApiQuery {}
|
||||
|
||||
export default class QueryClient extends CosmWasmClient implements IQueryClient {
|
||||
private nymdQuerier: NymdQuerier;
|
||||
|
||||
private validatorApiQuerier: ValidatorApiQuerier;
|
||||
|
||||
private constructor(tmClient: Tendermint34Client, validatorApiUrl: string) {
|
||||
super(tmClient);
|
||||
this.nymdQuerier = new NymdQuerier(this);
|
||||
this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl);
|
||||
}
|
||||
|
||||
public static async connectWithNym(nymdUrl: string, validatorApiUrl: string): Promise<QueryClient> {
|
||||
const tmClient = await Tendermint34Client.connect(nymdUrl);
|
||||
return new QueryClient(tmClient, validatorApiUrl);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
|
||||
return this.nymdQuerier.getCurrentRewardingInterval(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);
|
||||
}
|
||||
|
||||
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.nymdQuerier.getEpochRewardPercent(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();
|
||||
}
|
||||
async changeValidator(url: string): Promise<void> {
|
||||
this.cosmClient = await CosmWasmClient.connect(url)
|
||||
}
|
||||
|
||||
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, stakeDenom: string): Promise<Coin | null> {
|
||||
return this.cosmClient.getBalance(address, stakeDenom);
|
||||
}
|
||||
|
||||
public getStateParams(contractAddress: string): Promise<StateParams> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,467 +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,
|
||||
RewardingIntervalResponse,
|
||||
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>;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
|
||||
return this.nymdQuerier.getCurrentRewardingInterval(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);
|
||||
}
|
||||
|
||||
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.nymdQuerier.getEpochRewardPercent(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,
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,26 @@
|
||||
import axios from 'axios';
|
||||
import { GasPrice } from '@cosmjs/stargate';
|
||||
import axios from "axios";
|
||||
import { GasLimits, GasPrice } from "@cosmjs/stargate";
|
||||
import { CosmWasmFeeTable, defaultGasLimits } from "@cosmjs/cosmwasm-stargate";
|
||||
|
||||
export const nymGasLimits: GasLimits<CosmWasmFeeTable> = {
|
||||
...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 {
|
||||
if (typeof prefix === 'string') {
|
||||
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> => {
|
||||
const r = await axios.get(url, { responseType: 'arraybuffer' });
|
||||
if (r.status !== 200) {
|
||||
throw new Error(`Download error: ${r.status}`);
|
||||
}
|
||||
return r.data;
|
||||
const r = await axios.get(url, {responseType: "arraybuffer"});
|
||||
if (r.status !== 200) {
|
||||
throw new Error(`Download error: ${r.status}`);
|
||||
}
|
||||
return r.data;
|
||||
};
|
||||
|
||||
|
||||
+112
-149
@@ -1,162 +1,125 @@
|
||||
import { Coin } from '@cosmjs/stargate';
|
||||
import { Coin } from "@cosmjs/stargate";
|
||||
|
||||
// TODO: ideally we'd have re-exported those using that fancy crate that builds ts types from rust
|
||||
|
||||
export type MixnetContractVersion = {
|
||||
build_timestamp: string;
|
||||
build_version: string;
|
||||
commit_sha: string;
|
||||
commit_timestamp: string;
|
||||
commit_branch: string;
|
||||
rustc_version: string;
|
||||
};
|
||||
|
||||
/// One page of a possible multi-page set of mixnodes. The paging interface is quite
|
||||
/// inconvenient, as we don't have the two pieces of information we need to know
|
||||
/// in order to do paging nicely (namely `currentPage` and `totalPages` parameters).
|
||||
///
|
||||
/// Instead, we have only `start_next_page_after`, i.e. the key of the last record
|
||||
/// on this page. In order to get the *next* page, CosmWasm looks at that value,
|
||||
/// finds the next record, and builds the next page starting there. This happens
|
||||
/// **in series** rather than **in parallel** (!).
|
||||
///
|
||||
/// So we have some consistency problems:
|
||||
///
|
||||
/// * we can't make requests at a given block height, so the result set
|
||||
/// which we assemble over time may change while requests are being made.
|
||||
/// * at some point we will make a request for a `start_next_page_after` key
|
||||
/// which has just been deleted from the database.
|
||||
///
|
||||
/// TODO: more robust error handling on the "deleted key" case.
|
||||
export type PagedMixnodeResponse = {
|
||||
nodes: MixNodeBond[];
|
||||
per_page: number;
|
||||
start_next_after?: string;
|
||||
};
|
||||
|
||||
export type PagedGatewayResponse = {
|
||||
nodes: GatewayBond[];
|
||||
per_page: number;
|
||||
start_next_after?: string;
|
||||
};
|
||||
|
||||
export type MixOwnershipResponse = {
|
||||
address: string;
|
||||
mixnode?: MixNodeBond;
|
||||
};
|
||||
|
||||
export type GatewayOwnershipResponse = {
|
||||
address: string;
|
||||
gateway?: GatewayBond;
|
||||
};
|
||||
|
||||
export type ContractStateParams = {
|
||||
// ideally I'd want to define those as `number` rather than `string`, but
|
||||
// rust-side they are defined as Uint128 and that don't have
|
||||
// native javascript representations and therefore are interpreted as strings after deserialization
|
||||
minimum_mixnode_pledge: string;
|
||||
minimum_gateway_pledge: string;
|
||||
mixnode_rewarded_set_size: number;
|
||||
mixnode_active_set_size: number;
|
||||
};
|
||||
|
||||
export type RewardingIntervalResponse = {
|
||||
current_rewarding_interval_starting_block: number;
|
||||
current_rewarding_interval_nonce: number;
|
||||
rewarding_in_progress: boolean;
|
||||
};
|
||||
|
||||
export type LayerDistribution = {
|
||||
gateways: number;
|
||||
layer1: number;
|
||||
layer2: number;
|
||||
layer3: number;
|
||||
};
|
||||
|
||||
export type Delegation = {
|
||||
owner: string;
|
||||
node_identity: string;
|
||||
amount: Coin;
|
||||
block_height: number;
|
||||
proxy?: string;
|
||||
};
|
||||
|
||||
export type PagedMixDelegationsResponse = {
|
||||
delegations: Delegation[];
|
||||
start_next_after?: string;
|
||||
};
|
||||
|
||||
export type PagedDelegatorDelegationsResponse = {
|
||||
delegations: Delegation[];
|
||||
start_next_after?: string;
|
||||
};
|
||||
|
||||
export type PagedAllDelegationsResponse = {
|
||||
delegations: Delegation[];
|
||||
start_next_after?: [string, string];
|
||||
};
|
||||
|
||||
export type RewardingResult = {
|
||||
operator_reward: string;
|
||||
total_delegator_reward: string;
|
||||
};
|
||||
|
||||
export type NodeRewardParams = {
|
||||
period_reward_pool: string;
|
||||
k: string;
|
||||
reward_blockstamp: number;
|
||||
circulating_supply: string;
|
||||
uptime: string;
|
||||
sybil_resistance_percent: number;
|
||||
};
|
||||
|
||||
export type DelegatorRewardParams = {
|
||||
node_reward_params: NodeRewardParams;
|
||||
sigma: number;
|
||||
profit_margin: number;
|
||||
node_profit: number;
|
||||
};
|
||||
|
||||
export type PendingDelegatorRewarding = {
|
||||
running_results: RewardingResult;
|
||||
next_start: string;
|
||||
rewarding_params: DelegatorRewardParams;
|
||||
};
|
||||
|
||||
export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding };
|
||||
|
||||
export type MixnodeRewardingStatusResponse = {
|
||||
status?: RewardingStatus;
|
||||
};
|
||||
|
||||
export enum Layer {
|
||||
Gateway,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
nodes: MixNodeBond[],
|
||||
per_page: number, // TODO: camelCase
|
||||
start_next_after: string, // TODO: camelCase
|
||||
}
|
||||
|
||||
export type MixNodeBond = {
|
||||
owner: string;
|
||||
mix_node: MixNode;
|
||||
layer: Layer;
|
||||
bond_amount: Coin;
|
||||
total_delegation: Coin;
|
||||
};
|
||||
// a temporary way of achieving the same paging behaviour for the gateways
|
||||
// the same points made for `PagedResponse` stand here.
|
||||
export type PagedGatewayResponse = {
|
||||
nodes: GatewayBond[],
|
||||
per_page: number, // TODO: camelCase
|
||||
start_next_after: string, // TODO: camelCase
|
||||
}
|
||||
|
||||
export type MixOwnershipResponse = {
|
||||
address: string,
|
||||
has_node: boolean,
|
||||
}
|
||||
|
||||
export type GatewayOwnershipResponse = {
|
||||
address: string,
|
||||
has_gateway: boolean,
|
||||
}
|
||||
|
||||
export type StateParams = {
|
||||
epoch_length: number,
|
||||
// ideally I'd want to define those as `number` rather than `string`, but
|
||||
// rust-side they are defined as Uint128 and Decimal that don't have
|
||||
// native javascript representations and therefore are interpreted as strings after deserialization
|
||||
minimum_mixnode_bond: string,
|
||||
minimum_gateway_bond: string,
|
||||
mixnode_bond_reward_rate: string,
|
||||
gateway_bond_reward_rate: string,
|
||||
mixnode_delegation_reward_rate: string,
|
||||
gateway_delegation_reward_rate: string,
|
||||
mixnode_active_set_size: number,
|
||||
gateway_active_set_size: number,
|
||||
}
|
||||
|
||||
export type Delegation = {
|
||||
owner: string,
|
||||
amount: Coin,
|
||||
}
|
||||
|
||||
export type PagedMixDelegationsResponse = {
|
||||
node_owner: string,
|
||||
delegations: Delegation[],
|
||||
start_next_after: string
|
||||
}
|
||||
|
||||
export type PagedGatewayDelegationsResponse = {
|
||||
node_owner: string,
|
||||
delegations: Delegation[],
|
||||
start_next_after: string
|
||||
}
|
||||
|
||||
|
||||
export enum Layer {
|
||||
Gateway,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
}
|
||||
|
||||
export type MixNodeBond = { // TODO: change name to MixNodeBond
|
||||
owner: string,
|
||||
mix_node: MixNode, // TODO: camelCase this later once everything else works
|
||||
layer: Layer,
|
||||
bond_amount: Coin,
|
||||
total_delegation: Coin,
|
||||
}
|
||||
|
||||
export type MixNode = {
|
||||
host: string;
|
||||
mix_port: number;
|
||||
verloc_port: number;
|
||||
http_api_port: number;
|
||||
sphinx_key: string;
|
||||
identity_key: string;
|
||||
version: string;
|
||||
};
|
||||
host: string,
|
||||
mix_port: number,
|
||||
verloc_port: number,
|
||||
http_api_port: number,
|
||||
sphinx_key: string, // TODO: camelCase this later once everything else works
|
||||
identity_key: string,
|
||||
version: string,
|
||||
}
|
||||
|
||||
export type GatewayBond = {
|
||||
owner: string;
|
||||
gateway: Gateway;
|
||||
|
||||
bond_amount: Coin;
|
||||
total_delegation: Coin;
|
||||
};
|
||||
owner: string
|
||||
gateway: Gateway,
|
||||
|
||||
bond_amount: Coin,
|
||||
total_delegation: Coin,
|
||||
}
|
||||
|
||||
export type Gateway = {
|
||||
host: string;
|
||||
mix_port: number;
|
||||
clients_port: number;
|
||||
location: string;
|
||||
sphinx_key: string;
|
||||
identity_key: string;
|
||||
version: string;
|
||||
};
|
||||
host: string,
|
||||
mix_port: number,
|
||||
clients_port: number,
|
||||
location: string,
|
||||
sphinx_key: string,
|
||||
identity_key: string,
|
||||
version: string
|
||||
}
|
||||
|
||||
export type SendRequest = {
|
||||
senderAddress: string;
|
||||
recipientAddress: string;
|
||||
transferAmount: readonly Coin[];
|
||||
};
|
||||
senderAddress: string,
|
||||
recipientAddress: string,
|
||||
transferAmount: readonly Coin[]
|
||||
}
|
||||
@@ -1,18 +1,14 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types
|
||||
import { Coin } from '@cosmjs/stargate';
|
||||
import { EncodeObject } from '@cosmjs/proto-signing';
|
||||
import { Coin } from "@cosmjs/stargate";
|
||||
import { EncodeObject } from "@cosmjs/proto-signing";
|
||||
|
||||
export function makeBankMsgSend(
|
||||
senderAddress: string,
|
||||
recipientAddress: string,
|
||||
transferAmount: readonly Coin[],
|
||||
): EncodeObject {
|
||||
return {
|
||||
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
|
||||
value: {
|
||||
fromAddress: senderAddress,
|
||||
toAddress: recipientAddress,
|
||||
amount: transferAmount,
|
||||
},
|
||||
};
|
||||
}
|
||||
export function makeBankMsgSend(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[]): EncodeObject {
|
||||
return {
|
||||
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
|
||||
value: {
|
||||
fromAddress: senderAddress,
|
||||
toAddress: recipientAddress,
|
||||
amount: transferAmount,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { GatewayBond, MixNodeBond } from './types';
|
||||
|
||||
export const VALIDATOR_API_VERSION = '/v1';
|
||||
export const VALIDATOR_API_GATEWAYS_PATH = `${VALIDATOR_API_VERSION}/gateways`;
|
||||
export const VALIDATOR_API_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes`;
|
||||
export const VALIDATOR_API_ACTIVE_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/active`;
|
||||
export const VALIDATOR_API_REWARDED_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/rewarded`;
|
||||
|
||||
export interface IValidatorApiQuery {
|
||||
getCachedMixnodes(): Promise<MixNodeBond[]>;
|
||||
|
||||
getCachedGateways(): Promise<GatewayBond[]>;
|
||||
|
||||
getActiveMixnodes(): Promise<MixNodeBond[]>;
|
||||
|
||||
getRewardedMixnodes(): Promise<MixNodeBond[]>;
|
||||
}
|
||||
|
||||
export default class ValidatorApiQuerier implements IValidatorApiQuery {
|
||||
validatorApiUrl: string;
|
||||
|
||||
constructor(validatorApiUrl: string) {
|
||||
this.validatorApiUrl = validatorApiUrl;
|
||||
}
|
||||
|
||||
async getCachedMixnodes(): Promise<MixNodeBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_MIXNODES_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error('None of the provided validator APIs seem to be alive');
|
||||
}
|
||||
|
||||
async getCachedGateways(): Promise<GatewayBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_GATEWAYS_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error('None of the provided validator APIs seem to be alive');
|
||||
}
|
||||
|
||||
async getActiveMixnodes(): Promise<MixNodeBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_ACTIVE_MIXNODES_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error('None of the provided validator APIs seem to be alive');
|
||||
}
|
||||
|
||||
async getRewardedMixnodes(): Promise<MixNodeBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_REWARDED_MIXNODES_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error('None of the provided validator APIs seem to be alive');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { assert } from "chai";
|
||||
import INetClient from "../../src/net-client";
|
||||
import { Fixtures } from "../fixtures"
|
||||
import { Mock, Times } from "moq.ts";
|
||||
import GatewaysCache from "../../src/caches/gateways";
|
||||
|
||||
describe("Caching gateways: when the validator returns", () => {
|
||||
context("an empty list", () => {
|
||||
it("Should return an empty list", async () => {
|
||||
const perPage = 100;
|
||||
const contractAddress = "mockContractAddress";
|
||||
const emptyPromise = Promise.resolve(Fixtures.GatewaysResp.empty());
|
||||
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(emptyPromise);
|
||||
const cache = new GatewaysCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshGateways(contractAddress);
|
||||
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
assert.deepEqual([], cache.gateways);
|
||||
});
|
||||
})
|
||||
|
||||
context("a list of gatways that fits in a page", () => {
|
||||
it("Should return that one page list", async () => {
|
||||
const perPage = 2;
|
||||
const contractAddress = "mockContractAddress";
|
||||
const onePagePromise = Promise.resolve(Fixtures.GatewaysResp.onePage());
|
||||
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(onePagePromise);
|
||||
const cache = new GatewaysCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshGateways(contractAddress);
|
||||
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
assert.deepEqual(Fixtures.Gateways.list2(), cache.gateways);
|
||||
})
|
||||
})
|
||||
|
||||
context("a list of gateways that is longer than one page", () => {
|
||||
it("Should return the full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult = Fixtures.GatewaysResp.page1of2();
|
||||
const halfPageResult = Fixtures.GatewaysResp.halfPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(instance => instance.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult));
|
||||
mockClient.setup(instance => instance.getGateways(contractAddress, perPage, fullPageResult.start_next_after)).returns(Promise.resolve(halfPageResult));
|
||||
const cache = new GatewaysCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
mockClient.verify(instance => instance.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
mockClient.verify(instance => instance.getGateways(contractAddress, perPage, fullPageResult.start_next_after), Times.Exactly(1));
|
||||
|
||||
assert.deepEqual(Fixtures.Gateways.list3(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
|
||||
context("a list of gateways that is two filled pages", () => {
|
||||
it("Should return the full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult1 = Fixtures.GatewaysResp.page1of2();
|
||||
const fullPageResult2 = Fixtures.GatewaysResp.fullPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
|
||||
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
|
||||
|
||||
const cache = new GatewaysCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
|
||||
|
||||
assert.deepEqual(Fixtures.Gateways.list4(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
|
||||
context("refreshing the cache twice", () => {
|
||||
it("returns one full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult1 = Fixtures.GatewaysResp.page1of2();
|
||||
const fullPageResult2 = Fixtures.GatewaysResp.fullPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
|
||||
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
|
||||
|
||||
const cache = new GatewaysCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
|
||||
|
||||
await cache.refreshGateways(contractAddress);
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(2));
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(2));
|
||||
|
||||
assert.deepEqual(Fixtures.Gateways.list4(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
import { assert } from "chai";
|
||||
import INetClient from "../../src/net-client";
|
||||
import { Fixtures } from "../fixtures"
|
||||
import { Mock, Times } from "moq.ts";
|
||||
import { MixnodesCache } from "../../src/caches/mixnodes"
|
||||
|
||||
describe("Caching mixnodes: when the validator returns", () => {
|
||||
context("an empty list", () => {
|
||||
it("Should return an empty list", async () => {
|
||||
const perPage = 100;
|
||||
const contractAddress = "mockContractAddress";
|
||||
const emptyPromise = Promise.resolve(Fixtures.MixNodesResp.empty());
|
||||
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(emptyPromise);
|
||||
const cache = new MixnodesCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshMixNodes(contractAddress);
|
||||
|
||||
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
assert.deepEqual([], cache.mixNodes);
|
||||
});
|
||||
})
|
||||
context("a list of nodes that fits in a page", () => {
|
||||
it("Should return that one page list", async () => {
|
||||
const perPage = 2;
|
||||
const contractAddress = "mockContractAddress";
|
||||
const onePagePromise = Promise.resolve(Fixtures.MixNodesResp.onePage());
|
||||
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(onePagePromise);
|
||||
const cache = new MixnodesCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshMixNodes(contractAddress);
|
||||
|
||||
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
assert.deepEqual(Fixtures.MixNodes.list2(), cache.mixNodes);
|
||||
})
|
||||
})
|
||||
|
||||
context("a list of nodes that is longer than one page", () => {
|
||||
it("Should return the full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult = Fixtures.MixNodesResp.page1of2();
|
||||
const halfPageResult = Fixtures.MixNodesResp.halfPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(instance => instance.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult));
|
||||
mockClient.setup(instance => instance.getMixNodes(contractAddress, perPage, fullPageResult.start_next_after)).returns(Promise.resolve(halfPageResult));
|
||||
const cache = new MixnodesCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
mockClient.verify(instance => instance.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
mockClient.verify(instance => instance.getMixNodes(contractAddress, perPage, fullPageResult.start_next_after), Times.Exactly(1));
|
||||
|
||||
assert.deepEqual(Fixtures.MixNodes.list3(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
|
||||
context("a list of nodes that is two filled pages", () => {
|
||||
it("Should return the full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult1 = Fixtures.MixNodesResp.page1of2();
|
||||
const fullPageResult2 = Fixtures.MixNodesResp.fullPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
|
||||
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
|
||||
|
||||
const cache = new MixnodesCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
|
||||
|
||||
assert.deepEqual(Fixtures.MixNodes.list4(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
|
||||
context("refreshing the cache twice", () => {
|
||||
it("returns one full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult1 = Fixtures.MixNodesResp.page1of2();
|
||||
const fullPageResult2 = Fixtures.MixNodesResp.fullPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
|
||||
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
|
||||
|
||||
const cache = new MixnodesCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
await cache.refreshMixNodes(contractAddress);
|
||||
// mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
// mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
|
||||
|
||||
assert.deepEqual(Fixtures.MixNodes.list4(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
import { coins } from "@cosmjs/launchpad";
|
||||
import {PagedGatewayResponse, PagedMixnodeResponse} from "../src/net-client";
|
||||
import {GatewayBond, MixNodeBond} from "../src/types"
|
||||
|
||||
export namespace Fixtures {
|
||||
export class MixNodes {
|
||||
static single(): MixNodeBond {
|
||||
return {
|
||||
amount: coins(666, "unym"),
|
||||
owner: "bob",
|
||||
mix_node: {
|
||||
host: "1.1.1.1",
|
||||
layer: 1,
|
||||
location: "London, UK",
|
||||
sphinx_key: "foo",
|
||||
version: "0.10.0",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static list1(): MixNodeBond[] {
|
||||
return [MixNodes.single()]
|
||||
}
|
||||
|
||||
static list2(): MixNodeBond[] {
|
||||
return [MixNodes.single(), MixNodes.single()]
|
||||
}
|
||||
|
||||
static list3(): MixNodeBond[] {
|
||||
return [MixNodes.single(), MixNodes.single(), MixNodes.single()]
|
||||
}
|
||||
|
||||
static list4(): MixNodeBond[] {
|
||||
return [MixNodes.single(), MixNodes.single(), MixNodes.single(), MixNodes.single()]
|
||||
}
|
||||
}
|
||||
|
||||
export class MixNodesResp {
|
||||
static empty(): PagedResponse {
|
||||
return {
|
||||
nodes: [],
|
||||
per_page: 2,
|
||||
start_next_after: null,
|
||||
}
|
||||
}
|
||||
|
||||
static onePage(): PagedResponse {
|
||||
return {
|
||||
nodes: MixNodes.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: null
|
||||
}
|
||||
}
|
||||
|
||||
static page1of2(): PagedResponse {
|
||||
return {
|
||||
nodes: MixNodes.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: "2"
|
||||
}
|
||||
}
|
||||
|
||||
static halfPage2of2(): PagedResponse {
|
||||
return {
|
||||
nodes: MixNodes.list1(),
|
||||
per_page: 2,
|
||||
start_next_after: null
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static fullPage2of2(): PagedResponse {
|
||||
return {
|
||||
nodes: MixNodes.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Gateways {
|
||||
static single(): GatewayBond {
|
||||
return {
|
||||
amount: coins(666, "unym"),
|
||||
owner: "bob",
|
||||
gateway: {
|
||||
mix_host: "1.1.1.1:1234",
|
||||
clients_host: "ws://1.1.1.1:1235",
|
||||
location: "London, UK",
|
||||
identity_key: "bar",
|
||||
sphinx_key: "foo",
|
||||
version: "0.10.0",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static list1(): GatewayBond[] {
|
||||
return [Gateways.single()]
|
||||
}
|
||||
|
||||
static list2(): GatewayBond[] {
|
||||
return [Gateways.single(), Gateways.single()]
|
||||
}
|
||||
|
||||
static list3(): GatewayBond[] {
|
||||
return [Gateways.single(), Gateways.single(), Gateways.single()]
|
||||
}
|
||||
|
||||
static list4(): GatewayBond[] {
|
||||
return [Gateways.single(), Gateways.single(), Gateways.single(), Gateways.single()]
|
||||
}
|
||||
}
|
||||
|
||||
export class GatewaysResp {
|
||||
static empty(): PagedGatewayResponse {
|
||||
return {
|
||||
nodes: [],
|
||||
per_page: 2,
|
||||
start_next_after: "",
|
||||
}
|
||||
}
|
||||
|
||||
static onePage(): PagedGatewayResponse {
|
||||
return {
|
||||
nodes: Gateways.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: "",
|
||||
}
|
||||
}
|
||||
|
||||
static page1of2(): PagedGatewayResponse {
|
||||
return {
|
||||
nodes: Gateways.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: "2"
|
||||
}
|
||||
}
|
||||
|
||||
static halfPage2of2(): PagedGatewayResponse {
|
||||
return {
|
||||
nodes: Gateways.list1(),
|
||||
per_page: 2,
|
||||
start_next_after: "",
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static fullPage2of2(): PagedGatewayResponse {
|
||||
return {
|
||||
nodes: Gateways.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import validatorClient from "../../src/index";
|
||||
import { config } from '../test-utils/config';
|
||||
|
||||
let client: validatorClient;
|
||||
let mnemonic: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
mnemonic = validatorClient.randomMnemonic();
|
||||
client = await validatorClient.connect(
|
||||
mnemonic,
|
||||
config.NYMD_URL as string,
|
||||
config.VALIDATOR_API as string,
|
||||
config.CURRENCY_PREFIX as string,
|
||||
config.MIXNET_CONTRACT as string,
|
||||
config.VESTING_CONTRACT as string
|
||||
);
|
||||
});
|
||||
|
||||
//todos
|
||||
//we want to mock the majority of these tests
|
||||
//and keep a few integration tests in place
|
||||
|
||||
describe("connect to the nym validator client and perform integration tests against the current testnet", () => {
|
||||
test("get cached mixnodes", async () => {
|
||||
try {
|
||||
const response = await client.getCachedMixnodes();
|
||||
//expect all mixnodes to have their owner address
|
||||
response.forEach(mixnodeDetails => {
|
||||
expect(mixnodeDetails.owner).toHaveLength(43)
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get balance of address and denom of the network", async () => {
|
||||
try {
|
||||
//provide a users address and get their balance
|
||||
//we expect their balance to be zero, as it's a new account
|
||||
const address = await validatorClient.mnemonicToAddress(mnemonic, config.CURRENCY_PREFIX as string);
|
||||
const response = await client.getBalance(address);
|
||||
expect(response.amount).toStrictEqual("0");
|
||||
expect(response.denom).toBe("unymt");
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get minimium pledge amount for a mixnode", async () => {
|
||||
try {
|
||||
const response = await client.minimumMixnodePledge();
|
||||
expect(response.amount).toBe("100000000");
|
||||
expect(response.denom).toBe(config.CURRENCY_PREFIX as string);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get minimium gateway pledge amount", async () => {
|
||||
try {
|
||||
const response = await client.minimumGatewayPledge();
|
||||
expect(response.amount).toBe("100000000");
|
||||
expect(response.denom).toBe(config.CURRENCY_PREFIX as string);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get current mixnet contract address", () => {
|
||||
try {
|
||||
const response = client.mixnetContract;
|
||||
expect(response).toStrictEqual(config.MIXNET_CONTRACT as string)
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get current vesting contract address", () => {
|
||||
try {
|
||||
const response = client.vestingContract;
|
||||
expect(response).toStrictEqual(config.VESTING_CONTRACT as string)
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
import SigningClient from '../../src/signing-client';
|
||||
import validator from "../../src/index";
|
||||
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
|
||||
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
|
||||
import { config } from '../test-utils/config';
|
||||
|
||||
let cosmWasmClient: CosmWasmClient;
|
||||
let signingClient: SigningClient;
|
||||
let mnemonic: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
cosmWasmClient = await SigningClient.connect(config.NYMD_URL as string);
|
||||
|
||||
mnemonic = validator.randomMnemonic();
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic)
|
||||
signingClient = await SigningClient.connectWithNymSigner(
|
||||
wallet,
|
||||
config.NYMD_URL as string,
|
||||
config.VALIDATOR_API as string,
|
||||
config.CURRENCY_PREFIX as string);
|
||||
});
|
||||
|
||||
describe("alternate between the signing clients of nym and perform basic requests", () => {
|
||||
test("retrieve balance using the cosmwasm client", async () => {
|
||||
try {
|
||||
const randomAddress = await validator.mnemonicToAddress(mnemonic, config.CURRENCY_PREFIX as string);
|
||||
const balance = await cosmWasmClient.getBalance(randomAddress, config.CURRENCY_PREFIX as string);
|
||||
expect(balance.denom).toStrictEqual(config.CURRENCY_PREFIX as string);
|
||||
expect(balance.amount).toStrictEqual("0");
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
test("get the chain id of the network", async () => {
|
||||
try {
|
||||
//pass these values in environment variables in the future
|
||||
const chainId = await cosmWasmClient.getChainId();
|
||||
expect(chainId).toStrictEqual(config.CHAIN_ID as string);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
test("get height then search its block", async () => {
|
||||
try {
|
||||
const height = await cosmWasmClient.getHeight()
|
||||
const block = await cosmWasmClient.getBlock(height);
|
||||
expect(block.header.chainId).toStrictEqual(config.CHAIN_ID as string);
|
||||
expect(block.header.height).toStrictEqual(height);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
test("get current reward pool", async () => {
|
||||
try {
|
||||
//this is due to change due to when rewards get distributed
|
||||
const rewards = await signingClient.getRewardPool(config.MIXNET_CONTRACT as string);
|
||||
expect(rewards).toStrictEqual("250000000000000");
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,70 +0,0 @@
|
||||
import ValidatorApiQuerier from '../../src/validator-api-querier';
|
||||
import { config } from '../test-utils/config';
|
||||
import { now, elapsed} from '../test-utils/utils';
|
||||
|
||||
let client: ValidatorApiQuerier;
|
||||
|
||||
beforeEach(() => {
|
||||
client = new ValidatorApiQuerier(config.VALIDATOR_API as string);
|
||||
});
|
||||
|
||||
//todos
|
||||
//we want to mock the majority of these tests
|
||||
//and keep a few integration tests in place
|
||||
|
||||
describe("call out to the validator api and run queries", () => {
|
||||
test.skip("build client and get all mixnodes", async () => {
|
||||
try {
|
||||
//this test was currently ran against a set of prefix data
|
||||
//this will change
|
||||
const ownerAddress = "nymt1ydqkmz0ddpvkd3l0vyf8k5xjrqtcnxxvhlpdsr";
|
||||
let response = await client.getActiveMixnodes();
|
||||
expect(response[0].owner).toStrictEqual(ownerAddress);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get rewarded mixnodes", async () => {
|
||||
try {
|
||||
// we assume that all mixnodes will have their owners address
|
||||
// also active sets will determine rewarded mixnodes
|
||||
let response = await client.getRewardedMixnodes();
|
||||
|
||||
response.forEach(rNode => {
|
||||
expect(rNode.owner.length).toStrictEqual(43);
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
test("get cached gateways and it should be six", async () => {
|
||||
try {
|
||||
//current gateways running in sandbox-testnet 6
|
||||
let response = await client.getCachedGateways();
|
||||
expect(response.length).toStrictEqual(6);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test("get cached mixnodes", async () => {
|
||||
try {
|
||||
const start = now();
|
||||
let response = await client.getCachedMixnodes();
|
||||
response.forEach(mixnode => {
|
||||
expect(mixnode.owner.length).toStrictEqual(43);
|
||||
})
|
||||
console.log(elapsed(start, true));
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export const config = {
|
||||
NYMD_URL: process.env.NYMD_URL,
|
||||
VALIDATOR_API: process.env.VALIDATOR_API,
|
||||
MIXNET_CONTRACT: process.env.MIXNET_CONTRACT,
|
||||
VESTING_CONTRACT: process.env.MIXNET_CONTRACT,
|
||||
USER_MNEMONIC: process.env.MNEMONIC,
|
||||
CURRENCY_PREFIX: process.env.CURRENCY_PREFIX,
|
||||
CHAIN_ID: process.env.CHAIN_ID
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// timer actions
|
||||
// Store current time as `start`
|
||||
export const now = (eventName = null) => {
|
||||
if (eventName) {
|
||||
console.log(`Started ${eventName}..`);
|
||||
}
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
//takes arg of start time
|
||||
export const elapsed = (beginning: number, log = false) => {
|
||||
const duration = new Date().getTime() - beginning;
|
||||
if (log) {
|
||||
console.log(`${duration / 1000}s`);
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
const currency = require('../../src/currency');
|
||||
|
||||
describe("provide unit tests around the the currency module", () => {
|
||||
test.skip("convert to native balance", () => {
|
||||
const decimal = "12.0346";
|
||||
const value = currency.printableBalanceToNative(decimal);
|
||||
expect(value).toStrictEqual("12034600");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
const stargate = require("../../src/stargate-helper");
|
||||
import { config } from '../test-utils/config';
|
||||
|
||||
describe("test the stargate functions within the client", () => {
|
||||
test("test that the gas price is returned correctly", () => {
|
||||
const nymCurrency = config.CURRENCY_PREFIX as string;
|
||||
const getGasPrice = stargate.nymGasPrice(nymCurrency);
|
||||
expect(getGasPrice.denom).toBe(`u${nymCurrency}`);
|
||||
});
|
||||
|
||||
test("provide invalid type returns an error message", () => {
|
||||
//pass invalid type
|
||||
expect(() => {
|
||||
const nymCurrency = 13;
|
||||
stargate.nymGasPrice(nymCurrency);
|
||||
}).toThrow("13 is not of type string");
|
||||
});
|
||||
|
||||
//provide test for downloading wasm
|
||||
//mock this test
|
||||
// test.skip("providing nothing returns", async () => {
|
||||
// //pass invalid type
|
||||
// const downloadWasm = stargate.downloadWasm("http://localhost");
|
||||
// console.log(downloadWasm);
|
||||
// })
|
||||
});
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import validator from "../../src/index";
|
||||
import { config } from '../test-utils/config';
|
||||
|
||||
const NETWORK_DENOM = config.CURRENCY_PREFIX as string;
|
||||
|
||||
describe("perform basic interactions with the validator", () => {
|
||||
test("build client and get all mixnodes", async () => {
|
||||
const mnemonic = validator.randomMnemonic();
|
||||
const mnemonicCount = mnemonic.split(" ").length;
|
||||
expect(mnemonicCount).toStrictEqual(24);
|
||||
});
|
||||
|
||||
test("build client and get all mixnodes", async () => {
|
||||
const buildMnemonic = validator.randomMnemonic();
|
||||
const mnemonic = await validator.mnemonicToAddress(buildMnemonic, NETWORK_DENOM);
|
||||
expect(mnemonic).toHaveLength(43);
|
||||
expect(mnemonic).toContain(NETWORK_DENOM);
|
||||
});
|
||||
});
|
||||
@@ -5,11 +5,7 @@
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
]
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"typedocOptions": {
|
||||
"entryPoints": [
|
||||
@@ -20,11 +16,7 @@
|
||||
"exclude": [
|
||||
"dist",
|
||||
"examples",
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"tests",
|
||||
"./tests/*/*.tsx",
|
||||
"./tests/*/*.ts"
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
+1248
-2445
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "nym-client-wasm"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
version = "0.12.0"
|
||||
version = "0.11.0"
|
||||
edition = "2018"
|
||||
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
use rand::rngs::OsRng;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::{hash_to_scalar, Parameters};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::bandwidth::{BandwidthVoucherAttributes, BANDWIDTH_VALUE, TOTAL_ATTRIBUTES};
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::preparer::MessagePreparer;
|
||||
use rand::rngs::OsRng;
|
||||
use received_processor::ReceivedMessagesProcessor;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use topology::{gateway, nym_topology_from_bonds, NymTopology};
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use wasm_utils::{console_log, console_warn};
|
||||
|
||||
pub(crate) mod received_processor;
|
||||
@@ -27,7 +36,6 @@ const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
#[wasm_bindgen]
|
||||
pub struct NymClient {
|
||||
validator_server: Url,
|
||||
testnet_mode: bool,
|
||||
|
||||
// TODO: technically this doesn't need to be an Arc since wasm is run on a single thread
|
||||
// however, once we eventually combine this code with the native-client's, it will make things
|
||||
@@ -73,7 +81,6 @@ impl NymClient {
|
||||
|
||||
on_message: None,
|
||||
on_gateway_connect: None,
|
||||
testnet_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,11 +93,6 @@ impl NymClient {
|
||||
self.on_gateway_connect = Some(on_connect)
|
||||
}
|
||||
|
||||
pub fn set_testnet_mode(&mut self, testnet_mode: bool) {
|
||||
console_log!("Setting testnet mode to {}", testnet_mode);
|
||||
self.testnet_mode = testnet_mode;
|
||||
}
|
||||
|
||||
fn self_recipient(&self) -> Recipient {
|
||||
Recipient::new(
|
||||
*self.identity.public_key(),
|
||||
@@ -106,17 +108,51 @@ impl NymClient {
|
||||
self.self_recipient().to_string()
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn prepare_coconut_credential(validators: &[Url], identity_bytes: &[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 = credentials::bandwidth::obtain_signature(
|
||||
¶ms,
|
||||
&bandwidth_credential_attributes,
|
||||
validators,
|
||||
)
|
||||
.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(
|
||||
identity_bytes,
|
||||
&bandwidth_credential,
|
||||
&bandwidth_credential_attributes,
|
||||
&verification_key,
|
||||
)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
// Right now it's impossible to have async exported functions to take `&self` rather than self
|
||||
pub async fn initial_setup(self) -> Self {
|
||||
let testnet_mode = self.testnet_mode;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
let bandwidth_controller = Some(BandwidthController::new(
|
||||
vec![self.validator_server.clone()],
|
||||
*self.identity.public_key(),
|
||||
));
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let bandwidth_controller = None;
|
||||
let coconut_credential = {
|
||||
let validator_server = self.validator_server.clone();
|
||||
let identity_public_key = self.identity.public_key().clone();
|
||||
Self::prepare_coconut_credential(
|
||||
&vec![validator_server],
|
||||
&identity_public_key.to_bytes(),
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
let mut client = self.get_and_update_topology().await;
|
||||
let gateway = client.choose_gateway();
|
||||
@@ -132,15 +168,13 @@ impl NymClient {
|
||||
mixnet_messages_sender,
|
||||
ack_sender,
|
||||
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
bandwidth_controller,
|
||||
);
|
||||
|
||||
if testnet_mode {
|
||||
gateway_client.set_testnet_mode(true)
|
||||
}
|
||||
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
Some(coconut_credential),
|
||||
)
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "bandwidth-claim-contract"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Serializable structures for what we find in common/crypto
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct PublicKey([u8; 32]);
|
||||
|
||||
impl PublicKey {
|
||||
pub fn new(bytes: [u8; 32]) -> Self {
|
||||
PublicKey(bytes)
|
||||
}
|
||||
pub fn to_bytes(&self) -> [u8; 32] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for PublicKey {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct Signature([u8; 32], [u8; 32]);
|
||||
|
||||
impl Signature {
|
||||
pub fn new(bytes: [u8; 64]) -> Self {
|
||||
let mut sig1 = [0u8; 32];
|
||||
let mut sig2 = [0u8; 32];
|
||||
sig1.copy_from_slice(&bytes[..32]);
|
||||
sig2.copy_from_slice(&bytes[32..]);
|
||||
|
||||
Signature(sig1, sig2)
|
||||
}
|
||||
pub fn to_bytes(&self) -> [u8; 64] {
|
||||
let mut res = [0u8; 64];
|
||||
res[..32].copy_from_slice(&self.0);
|
||||
res[32..].copy_from_slice(&self.1);
|
||||
res
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod keys;
|
||||
pub mod msg;
|
||||
pub mod payment;
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::keys::PublicKey;
|
||||
use crate::payment::LinkPaymentData;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct InstantiateMsg {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
LinkPayment { data: LinkPaymentData },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QueryMsg {
|
||||
GetPayments {
|
||||
limit: Option<u32>,
|
||||
start_after: Option<PublicKey>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {}
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::keys::{PublicKey, Signature};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct Payment {
|
||||
verification_key: PublicKey,
|
||||
gateway_identity: PublicKey,
|
||||
bandwidth: u64,
|
||||
}
|
||||
|
||||
impl Payment {
|
||||
pub fn new(verification_key: PublicKey, gateway_identity: PublicKey, bandwidth: u64) -> Self {
|
||||
Payment {
|
||||
verification_key,
|
||||
gateway_identity,
|
||||
bandwidth,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verification_key(&self) -> PublicKey {
|
||||
self.verification_key
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct LinkPaymentData {
|
||||
pub verification_key: PublicKey,
|
||||
pub gateway_identity: PublicKey,
|
||||
pub bandwidth: u64,
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
impl LinkPaymentData {
|
||||
pub fn new(
|
||||
verification_key: [u8; 32],
|
||||
gateway_identity: [u8; 32],
|
||||
bandwidth: u64,
|
||||
signature: [u8; 64],
|
||||
) -> Self {
|
||||
LinkPaymentData {
|
||||
verification_key: PublicKey::new(verification_key),
|
||||
gateway_identity: PublicKey::new(gateway_identity),
|
||||
bandwidth,
|
||||
signature: Signature::new(signature),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedPaymentResponse {
|
||||
pub payments: Vec<Payment>,
|
||||
pub per_page: usize,
|
||||
pub start_next_after: Option<PublicKey>,
|
||||
}
|
||||
|
||||
impl PagedPaymentResponse {
|
||||
pub fn new(
|
||||
payments: Vec<Payment>,
|
||||
per_page: usize,
|
||||
start_next_after: Option<PublicKey>,
|
||||
) -> Self {
|
||||
PagedPaymentResponse {
|
||||
payments,
|
||||
per_page,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,19 +10,14 @@ edition = "2018"
|
||||
# TODO: (for this and other crates), similarly to 'tokio', import only required "futures" modules rather than
|
||||
# the entire crate
|
||||
futures = "0.3"
|
||||
json = "0.12.4"
|
||||
log = "0.4"
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
|
||||
# internal
|
||||
credentials = { path = "../../credentials" }
|
||||
crypto = { path = "../../crypto" }
|
||||
gateway-requests = { path = "../../../gateway/gateway-requests" }
|
||||
nymsphinx = { path = "../../nymsphinx" }
|
||||
coconut-interface = { path = "../../coconut-interface", optional = true }
|
||||
network-defaults = { path = "../../network-defaults" }
|
||||
|
||||
[dependencies.tungstenite]
|
||||
version = "0.13"
|
||||
@@ -36,12 +31,6 @@ features = ["macros", "rt", "net", "sync", "time"]
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
|
||||
version = "0.14"
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.secp256k1]
|
||||
version = "0.20.3"
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.web3]
|
||||
version = "0.17.0"
|
||||
|
||||
# wasm-only dependencies
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||
version = "0.2"
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::coconut::{
|
||||
bandwidth::{
|
||||
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
|
||||
},
|
||||
utils::obtain_aggregate_verification_key,
|
||||
};
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use credentials::token::bandwidth::TokenCredential;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crypto::asymmetric::identity;
|
||||
use crypto::asymmetric::identity::PublicKey;
|
||||
use network_defaults::BANDWIDTH_VALUE;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use network_defaults::{
|
||||
eth_contract::ETH_JSON_ABI, ETH_BURN_FUNCTION_NAME, ETH_CONTRACT_ADDRESS, ETH_MIN_BLOCK_DEPTH,
|
||||
TOKENS_TO_BURN,
|
||||
};
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use rand::rngs::OsRng;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use secp256k1::SecretKey;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use std::io::Write;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use std::str::FromStr;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use web3::{
|
||||
contract::{Contract, Options},
|
||||
transports::Http,
|
||||
types::{Address, Bytes, U256, U64},
|
||||
Web3,
|
||||
};
|
||||
|
||||
use crate::error::GatewayClientError;
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub fn eth_contract(web3: Web3<Http>) -> Contract<Http> {
|
||||
Contract::from_json(
|
||||
web3.eth(),
|
||||
Address::from(ETH_CONTRACT_ADDRESS),
|
||||
json::parse(ETH_JSON_ABI)
|
||||
.expect("Invalid json abi")
|
||||
.dump()
|
||||
.as_bytes(),
|
||||
)
|
||||
.expect("Invalid json abi")
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BandwidthController {
|
||||
#[cfg(feature = "coconut")]
|
||||
validator_endpoints: Vec<url::Url>,
|
||||
#[cfg(feature = "coconut")]
|
||||
identity: PublicKey,
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
contract: Contract<Http>,
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
eth_private_key: SecretKey,
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
backup_bandwidth_token_keys_dir: std::path::PathBuf,
|
||||
}
|
||||
|
||||
impl BandwidthController {
|
||||
#[cfg(feature = "coconut")]
|
||||
pub fn new(validator_endpoints: Vec<url::Url>, identity: PublicKey) -> Self {
|
||||
BandwidthController {
|
||||
validator_endpoints,
|
||||
identity,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub fn new(
|
||||
eth_endpoint: String,
|
||||
eth_private_key: String,
|
||||
backup_bandwidth_token_keys_dir: std::path::PathBuf,
|
||||
) -> Result<Self, GatewayClientError> {
|
||||
// Fail early, on invalid url
|
||||
let transport =
|
||||
Http::new(ð_endpoint).map_err(|_| GatewayClientError::InvalidURL(eth_endpoint))?;
|
||||
let web3 = web3::Web3::new(transport);
|
||||
// Fail early, on invalid abi
|
||||
let contract = eth_contract(web3);
|
||||
let eth_private_key = secp256k1::SecretKey::from_str(ð_private_key)
|
||||
.map_err(|_| GatewayClientError::InvalidEthereumPrivateKey)?;
|
||||
|
||||
Ok(BandwidthController {
|
||||
contract,
|
||||
eth_private_key,
|
||||
backup_bandwidth_token_keys_dir,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
fn backup_keypair(&self, keypair: &identity::KeyPair) -> Result<(), GatewayClientError> {
|
||||
std::fs::create_dir_all(&self.backup_bandwidth_token_keys_dir)?;
|
||||
let file_path = self
|
||||
.backup_bandwidth_token_keys_dir
|
||||
.join(keypair.public_key().to_base58_string());
|
||||
let mut file = std::fs::File::create(file_path)?;
|
||||
file.write_all(&keypair.private_key().to_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub async fn prepare_coconut_credential(
|
||||
&self,
|
||||
) -> Result<coconut_interface::Credential, GatewayClientError> {
|
||||
let verification_key = obtain_aggregate_verification_key(&self.validator_endpoints).await?;
|
||||
let params = coconut_interface::Parameters::new(TOTAL_ATTRIBUTES).unwrap();
|
||||
|
||||
// TODO: Decide what is the value and additional info associated with the bandwidth voucher
|
||||
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
|
||||
serial_number: params.random_scalar(),
|
||||
binding_number: params.random_scalar(),
|
||||
voucher_value: coconut_interface::hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
|
||||
voucher_info: coconut_interface::hash_to_scalar(
|
||||
String::from("BandwidthVoucher").as_bytes(),
|
||||
),
|
||||
};
|
||||
|
||||
let bandwidth_credential = obtain_signature(
|
||||
¶ms,
|
||||
&bandwidth_credential_attributes,
|
||||
&self.validator_endpoints,
|
||||
)
|
||||
.await?;
|
||||
// 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)
|
||||
Ok(prepare_for_spending(
|
||||
&self.identity.to_bytes(),
|
||||
&bandwidth_credential,
|
||||
&bandwidth_credential_attributes,
|
||||
&verification_key,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub async fn prepare_token_credential(
|
||||
&self,
|
||||
gateway_identity: PublicKey,
|
||||
) -> Result<TokenCredential, GatewayClientError> {
|
||||
let mut rng = OsRng;
|
||||
|
||||
let kp = identity::KeyPair::new(&mut rng);
|
||||
self.backup_keypair(&kp)?;
|
||||
|
||||
let verification_key = *kp.public_key();
|
||||
let signed_verification_key = kp.private_key().sign(&verification_key.to_bytes());
|
||||
self.buy_token_credential(verification_key, signed_verification_key)
|
||||
.await?;
|
||||
|
||||
let message: Vec<u8> = verification_key
|
||||
.to_bytes()
|
||||
.iter()
|
||||
.chain(gateway_identity.to_bytes().iter())
|
||||
.copied()
|
||||
.collect();
|
||||
let signature = kp.private_key().sign(&message);
|
||||
|
||||
Ok(TokenCredential::new(
|
||||
verification_key,
|
||||
gateway_identity,
|
||||
BANDWIDTH_VALUE,
|
||||
signature,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub async fn buy_token_credential(
|
||||
&self,
|
||||
verification_key: PublicKey,
|
||||
signed_verification_key: identity::Signature,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
// 0 means a transaction failure, 1 means success
|
||||
let confirmations = if cfg!(debug_assertions) {
|
||||
1
|
||||
} else {
|
||||
ETH_MIN_BLOCK_DEPTH
|
||||
};
|
||||
// 15 seconds per confirmation block + 10 seconds of network overhead
|
||||
log::info!(
|
||||
"Waiting for Ethereum transaction. This should take about {} seconds",
|
||||
confirmations * 15 + 10
|
||||
);
|
||||
let recipt = self
|
||||
.contract
|
||||
.signed_call_with_confirmations(
|
||||
ETH_BURN_FUNCTION_NAME,
|
||||
(
|
||||
U256::from(TOKENS_TO_BURN),
|
||||
U256::from(&verification_key.to_bytes()),
|
||||
Bytes(signed_verification_key.to_bytes().to_vec()),
|
||||
),
|
||||
Options::default(),
|
||||
confirmations,
|
||||
&self.eth_private_key,
|
||||
)
|
||||
.await?;
|
||||
if Some(U64::from(0)) == recipt.status {
|
||||
Err(GatewayClientError::BurnTokenError(
|
||||
web3::Error::InvalidResponse(format!(
|
||||
"Transaction status is 0 (failure): {:?}",
|
||||
recipt.logs,
|
||||
)),
|
||||
))
|
||||
} else {
|
||||
log::info!(
|
||||
"Bought bandwidth on Ethereum: {} MB",
|
||||
BANDWIDTH_VALUE / 1024 / 1024
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use network_defaults::ETH_EVENT_NAME;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_contract() {
|
||||
let transport =
|
||||
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
|
||||
let web3 = web3::Web3::new(transport);
|
||||
// test no panic occurs
|
||||
eth_contract(web3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_event_name_constant_against_abi() {
|
||||
let transport =
|
||||
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
|
||||
let web3 = web3::Web3::new(transport);
|
||||
let contract = eth_contract(web3);
|
||||
assert!(contract.abi().event(ETH_EVENT_NAME).is_ok());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::bandwidth::BandwidthController;
|
||||
use crate::cleanup_socket_message;
|
||||
use crate::error::GatewayClientError;
|
||||
use crate::packet_router::PacketRouter;
|
||||
@@ -9,10 +8,6 @@ pub use crate::packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
};
|
||||
use crate::socket_state::{PartiallyDelegated, SocketState};
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use credentials::token::bandwidth::TokenCredential;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
|
||||
@@ -35,12 +30,15 @@ use fluvio_wasm_timer as wasm_timer;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
|
||||
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
|
||||
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
|
||||
|
||||
pub struct GatewayClient {
|
||||
authenticated: bool,
|
||||
testnet_mode: bool,
|
||||
#[cfg(feature = "coconut")]
|
||||
bandwidth_remaining: i64,
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
@@ -49,7 +47,6 @@ pub struct GatewayClient {
|
||||
connection: SocketState,
|
||||
packet_router: PacketRouter,
|
||||
response_timeout_duration: Duration,
|
||||
bandwidth_controller: Option<BandwidthController>,
|
||||
|
||||
// reconnection related variables
|
||||
/// Specifies whether client should try to reconnect to gateway on connection failure.
|
||||
@@ -72,30 +69,26 @@ impl GatewayClient {
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
response_timeout_duration: Duration,
|
||||
bandwidth_controller: Option<BandwidthController>,
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
authenticated: false,
|
||||
testnet_mode: false,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address,
|
||||
|
||||
gateway_identity,
|
||||
local_identity,
|
||||
shared_key,
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender),
|
||||
response_timeout_duration,
|
||||
bandwidth_controller,
|
||||
should_reconnect_on_failure: true,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_testnet_mode(&mut self, testnet_mode: bool) {
|
||||
self.testnet_mode = testnet_mode
|
||||
}
|
||||
|
||||
// TODO: later convert into proper builder methods
|
||||
pub fn with_reconnection_on_failure(&mut self, should_reconnect_on_failure: bool) {
|
||||
self.should_reconnect_on_failure = should_reconnect_on_failure
|
||||
@@ -125,8 +118,10 @@ impl GatewayClient {
|
||||
|
||||
GatewayClient {
|
||||
authenticated: false,
|
||||
testnet_mode: false,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
bandwidth_remaining: 0,
|
||||
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
local_identity,
|
||||
@@ -134,7 +129,6 @@ impl GatewayClient {
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
response_timeout_duration,
|
||||
bandwidth_controller: None,
|
||||
should_reconnect_on_failure: false,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
@@ -145,10 +139,6 @@ impl GatewayClient {
|
||||
self.gateway_identity
|
||||
}
|
||||
|
||||
pub fn remaining_bandwidth(&self) -> i64 {
|
||||
self.bandwidth_remaining
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
@@ -213,7 +203,14 @@ impl GatewayClient {
|
||||
|
||||
for i in 1..self.reconnection_attempts {
|
||||
info!("attempt {}...", i);
|
||||
if self.authenticate_and_start().await.is_ok() {
|
||||
if self
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
info!("managed to reconnect!");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -232,7 +229,13 @@ impl GatewayClient {
|
||||
|
||||
// final attempt (done separately to be able to return a proper error)
|
||||
info!("attempt {}", self.reconnection_attempts);
|
||||
match self.authenticate_and_start().await {
|
||||
match self
|
||||
.authenticate_and_start(
|
||||
#[cfg(feature = "coconut")]
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
info!("managed to reconnect!");
|
||||
Ok(())
|
||||
@@ -443,12 +446,8 @@ impl GatewayClient {
|
||||
ClientControlRequest::new_authenticate(self_address, encrypted_address, iv).into();
|
||||
|
||||
match self.send_websocket_message(msg).await? {
|
||||
ServerResponse::Authenticate {
|
||||
status,
|
||||
bandwidth_remaining,
|
||||
} => {
|
||||
ServerResponse::Authenticate { status } => {
|
||||
self.authenticated = status;
|
||||
self.bandwidth_remaining = bandwidth_remaining;
|
||||
Ok(())
|
||||
}
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
@@ -474,15 +473,22 @@ impl GatewayClient {
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn claim_coconut_bandwidth(
|
||||
pub async fn claim_coconut_bandwidth(
|
||||
&mut self,
|
||||
credential: Credential,
|
||||
coconut_credential: Credential,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
if self.shared_key.is_none() {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
}
|
||||
|
||||
let mut rng = OsRng;
|
||||
let iv = IV::new_random(&mut rng);
|
||||
|
||||
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
|
||||
&credential,
|
||||
&coconut_credential,
|
||||
self.shared_key.as_ref().unwrap(),
|
||||
iv,
|
||||
)
|
||||
@@ -496,79 +502,7 @@ impl GatewayClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
async fn claim_token_bandwidth(
|
||||
&mut self,
|
||||
credential: TokenCredential,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
let mut rng = OsRng;
|
||||
|
||||
let iv = IV::new_random(&mut rng);
|
||||
|
||||
let msg = ClientControlRequest::new_enc_token_bandwidth_credential(
|
||||
&credential,
|
||||
self.shared_key.as_ref().unwrap(),
|
||||
iv,
|
||||
)
|
||||
.into();
|
||||
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
|
||||
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
_ => Err(GatewayClientError::UnexpectedResponse),
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_claim_testnet_bandwidth(&mut self) -> Result<(), GatewayClientError> {
|
||||
let msg = ClientControlRequest::ClaimFreeTestnetBandwidth.into();
|
||||
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
|
||||
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
_ => Err(GatewayClientError::UnexpectedResponse),
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError> {
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
if self.shared_key.is_none() {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
}
|
||||
if self.bandwidth_controller.is_none() {
|
||||
return Err(GatewayClientError::NoBandwidthControllerAvailable);
|
||||
}
|
||||
|
||||
warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while");
|
||||
if self.testnet_mode {
|
||||
info!("The client is running in testnet mode - attempting to claim bandwidth without a credential");
|
||||
return self.try_claim_testnet_bandwidth().await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
let credential = self
|
||||
.bandwidth_controller
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.prepare_coconut_credential()
|
||||
.await?;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let credential = self
|
||||
.bandwidth_controller
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.prepare_token_credential(self.gateway_identity)
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
return self.claim_coconut_bandwidth(credential).await;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
return self.claim_token_bandwidth(credential).await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
|
||||
packets
|
||||
.iter()
|
||||
@@ -583,16 +517,9 @@ impl GatewayClient {
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
if self.estimate_required_bandwidth(&packets) > self.bandwidth_remaining {
|
||||
// Try to claim more bandwidth first, and return an error only if that is still not
|
||||
// enough (the current granularity for bandwidth should be sufficient)
|
||||
self.claim_bandwidth().await?;
|
||||
if self.estimate_required_bandwidth(&packets) > self.bandwidth_remaining {
|
||||
return Err(GatewayClientError::NotEnoughBandwidth(
|
||||
self.estimate_required_bandwidth(&packets),
|
||||
self.bandwidth_remaining,
|
||||
));
|
||||
}
|
||||
#[cfg(feature = "coconut")]
|
||||
if self.estimate_required_bandwidth(&packets) < self.bandwidth_remaining {
|
||||
return Err(GatewayClientError::NotEnoughBandwidth);
|
||||
}
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
@@ -658,16 +585,9 @@ impl GatewayClient {
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
#[cfg(feature = "coconut")]
|
||||
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
|
||||
// Try to claim more bandwidth first, and return an error only if that is still not
|
||||
// enough
|
||||
self.claim_bandwidth().await?;
|
||||
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
|
||||
return Err(GatewayClientError::NotEnoughBandwidth(
|
||||
mix_packet.sphinx_packet().len() as i64,
|
||||
self.bandwidth_remaining,
|
||||
));
|
||||
}
|
||||
return Err(GatewayClientError::NotEnoughBandwidth);
|
||||
}
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
@@ -704,6 +624,10 @@ impl GatewayClient {
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
#[cfg(feature = "coconut")]
|
||||
if self.bandwidth_remaining <= 0 {
|
||||
return Err(GatewayClientError::NotEnoughBandwidth);
|
||||
}
|
||||
if self.connection.is_partially_delegated() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -731,12 +655,22 @@ impl GatewayClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn authenticate_and_start(&mut self) -> Result<Arc<SharedKeys>, GatewayClientError> {
|
||||
pub async fn authenticate_and_start(
|
||||
&mut self,
|
||||
#[cfg(feature = "coconut")] coconut_credential: Option<Credential>,
|
||||
) -> Result<Arc<SharedKeys>, GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
self.establish_connection().await?;
|
||||
}
|
||||
let shared_key = self.perform_initial_authentication().await?;
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
{
|
||||
if let Some(coconut_credential) = coconut_credential {
|
||||
self.claim_coconut_bandwidth(coconut_credential).await?;
|
||||
}
|
||||
}
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
|
||||
|
||||
@@ -2,85 +2,41 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use gateway_requests::registration::handshake::error::HandshakeError;
|
||||
use std::fmt::{self, Error, Formatter};
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
use tungstenite::Error as WsError;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::JsValue;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use web3::Error as Web3Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum GatewayClientError {
|
||||
#[error("Connection to the gateway is not established")]
|
||||
ConnectionNotEstablished,
|
||||
|
||||
#[error("Gateway returned an error response - {0}")]
|
||||
GatewayError(String),
|
||||
|
||||
#[error("There was a network error - {0}")]
|
||||
NetworkError(#[from] WsError),
|
||||
NetworkError(WsError),
|
||||
|
||||
// TODO: see if `JsValue` is a reasonable type for this
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[error("There was a network error")]
|
||||
NetworkErrorWasm(JsValue),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Could not backup keypair - {0}")]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Could not burn ERC20 token in Ethereum smart contract - {0}")]
|
||||
BurnTokenError(#[from] Web3Error),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Invalid Ethereum private key")]
|
||||
InvalidEthereumPrivateKey,
|
||||
|
||||
#[error("Invalid URL - {0}")]
|
||||
InvalidURL(String),
|
||||
|
||||
#[error("No shared key was provided or obtained")]
|
||||
NoSharedKeyAvailable,
|
||||
|
||||
#[error("No bandwidth controller provided")]
|
||||
NoBandwidthControllerAvailable,
|
||||
|
||||
#[error("Credential error - {0}")]
|
||||
CredentialError(#[from] credentials::error::Error),
|
||||
|
||||
#[error("Connection was abruptly closed")]
|
||||
ConnectionAbruptlyClosed,
|
||||
|
||||
#[error("Received response was malformed")]
|
||||
MalformedResponse,
|
||||
|
||||
#[error("Credential could not be serialized")]
|
||||
SerializeCredential,
|
||||
|
||||
#[error("Client is not authenticated")]
|
||||
NotAuthenticated,
|
||||
|
||||
#[error("Client does not have enough bandwidth: estimated {0}, remaining: {1}")]
|
||||
NotEnoughBandwidth(i64, i64),
|
||||
|
||||
#[error("Received an unexpected response")]
|
||||
NotEnoughBandwidth,
|
||||
UnexpectedResponse,
|
||||
|
||||
#[error("Connection is in an invalid state - please send a bug report")]
|
||||
ConnectionInInvalidState,
|
||||
|
||||
#[error("Failed to finish registration handshake - {0}")]
|
||||
RegistrationFailure(HandshakeError),
|
||||
|
||||
#[error("Authentication failure")]
|
||||
AuthenticationFailure,
|
||||
|
||||
#[error("Timed out")]
|
||||
Timeout,
|
||||
}
|
||||
|
||||
impl From<WsError> for GatewayClientError {
|
||||
fn from(err: WsError) -> Self {
|
||||
GatewayClientError::NetworkError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewayClientError {
|
||||
pub fn is_closed_connection(&self) -> bool {
|
||||
match self {
|
||||
@@ -98,3 +54,60 @@ impl GatewayClientError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl From<JsValue> for GatewayClientError {
|
||||
fn from(err: JsValue) -> Self {
|
||||
GatewayClientError::NetworkErrorWasm(err)
|
||||
}
|
||||
}
|
||||
|
||||
// better human readable representation of the error, mostly so that GatewayClientError
|
||||
// would implement std::error::Error
|
||||
impl fmt::Display for GatewayClientError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
match self {
|
||||
GatewayClientError::ConnectionNotEstablished => {
|
||||
write!(f, "connection to the gateway is not established")
|
||||
}
|
||||
GatewayClientError::NoSharedKeyAvailable => {
|
||||
write!(f, "no shared key was provided or obtained")
|
||||
}
|
||||
GatewayClientError::NotAuthenticated => write!(f, "client is not authenticated"),
|
||||
|
||||
GatewayClientError::NetworkError(err) => {
|
||||
write!(f, "there was a network error - {}", err)
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
GatewayClientError::NetworkErrorWasm(err) => {
|
||||
write!(f, "there was a network error - {:?}", err)
|
||||
}
|
||||
|
||||
GatewayClientError::ConnectionAbruptlyClosed => {
|
||||
write!(f, "connection was abruptly closed")
|
||||
}
|
||||
GatewayClientError::Timeout => write!(f, "timed out"),
|
||||
GatewayClientError::MalformedResponse => write!(f, "received response was malformed"),
|
||||
GatewayClientError::ConnectionInInvalidState => write!(
|
||||
f,
|
||||
"connection is in an invalid state - please send a bug report"
|
||||
),
|
||||
GatewayClientError::RegistrationFailure(handshake_err) => write!(
|
||||
f,
|
||||
"failed to finish registration handshake - {}",
|
||||
handshake_err
|
||||
),
|
||||
GatewayClientError::AuthenticationFailure => write!(f, "authentication failure"),
|
||||
GatewayClientError::GatewayError(err) => {
|
||||
write!(f, "gateway returned an error response - {}", err)
|
||||
}
|
||||
GatewayClientError::UnexpectedResponse => write!(f, "received an unexpected response"),
|
||||
GatewayClientError::NotEnoughBandwidth => {
|
||||
write!(f, "client does not have enough bandwidth")
|
||||
}
|
||||
GatewayClientError::SerializeCredential => {
|
||||
write!(f, "credential could not be serialized")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ pub use packet_router::{
|
||||
};
|
||||
use tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
pub mod bandwidth;
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod packet_router;
|
||||
|
||||
@@ -119,7 +119,7 @@ impl PartiallyDelegated {
|
||||
}
|
||||
.is_err()
|
||||
{
|
||||
warn!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
|
||||
panic!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ rust-version = "1.56"
|
||||
[dependencies]
|
||||
base64 = "0.13"
|
||||
mixnet-contract = { path="../../../common/mixnet-contract" }
|
||||
vesting-contract = { path="../../../contracts/vesting" }
|
||||
serde = { version="1", features=["derive"] }
|
||||
serde_json = "1"
|
||||
reqwest = { version="0.11", features=["json"] }
|
||||
@@ -27,14 +26,13 @@ network-defaults = { path = "../../network-defaults" }
|
||||
async-trait = { version = "0.1.51", optional = true }
|
||||
bip39 = { version = "1", features = ["rand"], optional = true }
|
||||
config = { path = "../../config", optional = true }
|
||||
#cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev="e5a1872083abb3d88fa62dda966e7f5408deba58", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
prost = { version = "0.9", default-features = false, optional = true }
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
itertools = { version = "0.10", optional = true }
|
||||
cosmwasm-std = { version = "1.0.0-beta3", optional = true }
|
||||
ts-rs = {version = "5.1", optional = true}
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true }
|
||||
ts-rs = "3.0"
|
||||
|
||||
[features]
|
||||
nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
|
||||
|
||||
@@ -6,18 +6,13 @@ use crate::nymd::{
|
||||
error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
|
||||
};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use mixnet_contract::ContractStateParams;
|
||||
use mixnet_contract::StateParams;
|
||||
|
||||
use crate::{validator_api, ValidatorClientError};
|
||||
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use mixnet_contract::{
|
||||
Delegation, MixnetContractVersion, MixnodeRewardingStatusResponse, RewardingIntervalResponse,
|
||||
};
|
||||
use mixnet_contract::RawDelegationData;
|
||||
use mixnet_contract::{GatewayBond, MixNodeBond};
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
@@ -25,7 +20,6 @@ pub struct Config {
|
||||
api_url: Url,
|
||||
nymd_url: Url,
|
||||
mixnet_contract_address: Option<cosmrs::AccountId>,
|
||||
vesting_contract_address: Option<cosmrs::AccountId>,
|
||||
|
||||
mixnode_page_limit: Option<u32>,
|
||||
gateway_page_limit: Option<u32>,
|
||||
@@ -38,12 +32,10 @@ impl Config {
|
||||
nymd_url: Url,
|
||||
api_url: Url,
|
||||
mixnet_contract_address: Option<cosmrs::AccountId>,
|
||||
vesting_contract_address: Option<cosmrs::AccountId>,
|
||||
) -> Self {
|
||||
Config {
|
||||
nymd_url,
|
||||
mixnet_contract_address,
|
||||
vesting_contract_address,
|
||||
api_url,
|
||||
mixnode_page_limit: None,
|
||||
gateway_page_limit: None,
|
||||
@@ -70,7 +62,6 @@ impl Config {
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub struct Client<C> {
|
||||
mixnet_contract_address: Option<cosmrs::AccountId>,
|
||||
vesting_contract_address: Option<cosmrs::AccountId>,
|
||||
mnemonic: Option<bip39::Mnemonic>,
|
||||
|
||||
mixnode_page_limit: Option<u32>,
|
||||
@@ -92,14 +83,11 @@ impl Client<SigningNymdClient> {
|
||||
let nymd_client = NymdClient::connect_with_mnemonic(
|
||||
config.nymd_url.as_str(),
|
||||
config.mixnet_contract_address.clone(),
|
||||
config.vesting_contract_address.clone(),
|
||||
mnemonic.clone(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok(Client {
|
||||
mixnet_contract_address: config.mixnet_contract_address,
|
||||
vesting_contract_address: config.vesting_contract_address,
|
||||
mnemonic: Some(mnemonic),
|
||||
mixnode_page_limit: config.mixnode_page_limit,
|
||||
gateway_page_limit: config.gateway_page_limit,
|
||||
@@ -113,9 +101,7 @@ impl Client<SigningNymdClient> {
|
||||
self.nymd = NymdClient::connect_with_mnemonic(
|
||||
new_endpoint.as_ref(),
|
||||
self.mixnet_contract_address.clone(),
|
||||
self.vesting_contract_address.clone(),
|
||||
self.mnemonic.clone().unwrap(),
|
||||
None,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -127,19 +113,16 @@ impl Client<QueryNymdClient> {
|
||||
let validator_api_client = validator_api::Client::new(config.api_url.clone());
|
||||
let nymd_client = NymdClient::connect(
|
||||
config.nymd_url.as_str(),
|
||||
config.mixnet_contract_address.clone().unwrap_or_else(|| {
|
||||
cosmrs::AccountId::from_str(network_defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS)
|
||||
.unwrap()
|
||||
}),
|
||||
config.vesting_contract_address.clone().unwrap_or_else(|| {
|
||||
cosmrs::AccountId::from_str(network_defaults::DEFAULT_VESTING_CONTRACT_ADDRESS)
|
||||
.unwrap()
|
||||
}),
|
||||
config
|
||||
.mixnet_contract_address
|
||||
.clone()
|
||||
.ok_or(ValidatorClientError::NymdError(
|
||||
NymdError::NoContractAddressAvailable,
|
||||
))?,
|
||||
)?;
|
||||
|
||||
Ok(Client {
|
||||
mixnet_contract_address: config.mixnet_contract_address,
|
||||
vesting_contract_address: config.vesting_contract_address,
|
||||
mnemonic: None,
|
||||
mixnode_page_limit: config.mixnode_page_limit,
|
||||
gateway_page_limit: config.gateway_page_limit,
|
||||
@@ -153,7 +136,6 @@ impl Client<QueryNymdClient> {
|
||||
self.nymd = NymdClient::connect(
|
||||
new_endpoint.as_ref(),
|
||||
self.mixnet_contract_address.clone().unwrap(),
|
||||
self.vesting_contract_address.clone().unwrap(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -183,69 +165,11 @@ impl<C> Client<C> {
|
||||
Ok(self.validator_api.get_gateways().await?)
|
||||
}
|
||||
|
||||
pub async fn get_contract_settings(&self) -> Result<ContractStateParams, ValidatorClientError>
|
||||
pub async fn get_state_params(&self) -> Result<StateParams, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_contract_settings().await?)
|
||||
}
|
||||
|
||||
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_mixnet_contract_version().await?)
|
||||
}
|
||||
|
||||
pub async fn get_current_rewarding_interval(
|
||||
&self,
|
||||
) -> Result<RewardingIntervalResponse, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_current_rewarding_interval().await?)
|
||||
}
|
||||
|
||||
pub async fn get_rewarding_status(
|
||||
&self,
|
||||
mix_identity: mixnet_contract::IdentityKey,
|
||||
rewarding_interval_nonce: u32,
|
||||
) -> Result<MixnodeRewardingStatusResponse, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self
|
||||
.nymd
|
||||
.get_rewarding_status(mix_identity, rewarding_interval_nonce)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_reward_pool().await?.u128())
|
||||
}
|
||||
|
||||
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_circulating_supply().await?.u128())
|
||||
}
|
||||
|
||||
pub async fn get_sybil_resistance_percent(&self) -> Result<u8, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_sybil_resistance_percent().await?)
|
||||
}
|
||||
|
||||
pub async fn get_epoch_reward_percent(&self) -> Result<u8, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self.nymd.get_epoch_reward_percent().await?)
|
||||
Ok(self.nymd.get_state_params().await?)
|
||||
}
|
||||
|
||||
// basically handles paging for us
|
||||
@@ -325,7 +249,9 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_network_delegations(&self) -> Result<Vec<Delegation>, ValidatorClientError>
|
||||
pub async fn get_all_nymd_mixnode_delegations(
|
||||
&self,
|
||||
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
@@ -334,7 +260,7 @@ impl<C> Client<C> {
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_all_network_delegations_paged(
|
||||
.get_all_mix_delegations_paged(
|
||||
start_after.take(),
|
||||
self.mixnode_delegations_page_limit,
|
||||
)
|
||||
@@ -351,10 +277,10 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_delegator_delegations(
|
||||
pub async fn get_all_nymd_reverse_mixnode_delegations(
|
||||
&self,
|
||||
delegation_owner: &cosmrs::AccountId,
|
||||
) -> Result<Vec<Delegation>, ValidatorClientError>
|
||||
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
@@ -363,13 +289,13 @@ impl<C> Client<C> {
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.nymd
|
||||
.get_delegator_delegations_paged(
|
||||
delegation_owner.to_string(),
|
||||
.get_reverse_mix_delegations_paged(
|
||||
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
|
||||
start_after.take(),
|
||||
self.mixnode_delegations_page_limit,
|
||||
)
|
||||
.await?;
|
||||
delegations.append(&mut paged_response.delegations);
|
||||
delegations.append(&mut paged_response.delegated_nodes);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res)
|
||||
@@ -381,6 +307,28 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_mixnode_delegations_of_owner(
|
||||
&self,
|
||||
delegation_owner: &cosmrs::AccountId,
|
||||
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let mut delegations = Vec::new();
|
||||
for node_identity in self
|
||||
.get_all_nymd_reverse_mixnode_delegations(delegation_owner)
|
||||
.await?
|
||||
{
|
||||
let delegation = self
|
||||
.nymd
|
||||
.get_mix_delegation(node_identity, delegation_owner)
|
||||
.await?;
|
||||
delegations.push(delegation);
|
||||
}
|
||||
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::nymd::cosmwasm_client::helpers::create_pagination;
|
||||
use crate::nymd::cosmwasm_client::types::{
|
||||
Account, Code, CodeDetails, Contract, ContractCodeHistoryEntry, ContractCodeId,
|
||||
SequenceResponse, SimulateResponse,
|
||||
SequenceResponse,
|
||||
};
|
||||
use crate::nymd::error::NymdError;
|
||||
use async_trait::async_trait;
|
||||
@@ -14,19 +14,15 @@ use cosmrs::proto::cosmos::auth::v1beta1::{
|
||||
use cosmrs::proto::cosmos::bank::v1beta1::{
|
||||
QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse,
|
||||
};
|
||||
use cosmrs::proto::cosmos::tx::v1beta1::{
|
||||
SimulateRequest, SimulateResponse as ProtoSimulateResponse,
|
||||
};
|
||||
use cosmrs::proto::cosmwasm::wasm::v1::*;
|
||||
use cosmrs::proto::cosmwasm::wasm::v1beta1::*;
|
||||
use cosmrs::rpc::endpoint::block::Response as BlockResponse;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::endpoint::tx::Response as TxResponse;
|
||||
use cosmrs::rpc::query::Query;
|
||||
use cosmrs::rpc::{self, HttpClient, Order};
|
||||
use cosmrs::tendermint::abci::Code as AbciCode;
|
||||
use cosmrs::tendermint::abci::Transaction;
|
||||
use cosmrs::tendermint::{abci, block, chain};
|
||||
use cosmrs::{tx, AccountId, Coin, Denom, Tx};
|
||||
use cosmrs::{tx, AccountId, Coin, Denom};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
@@ -53,11 +49,6 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
|
||||
let res = self.abci_query(path, buf, None, false).await?;
|
||||
|
||||
match res.code {
|
||||
AbciCode::Err(code) => return Err(NymdError::AbciError(code, res.log)),
|
||||
AbciCode::Ok => (),
|
||||
}
|
||||
|
||||
Ok(Res::decode(res.value.as_ref())?)
|
||||
}
|
||||
|
||||
@@ -222,7 +213,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
}
|
||||
|
||||
async fn get_codes(&self) -> Result<Vec<Code>, NymdError> {
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/Codes".parse().unwrap());
|
||||
let path = Some("/cosmwasm.wasm.v1beta1.Query/Codes".parse().unwrap());
|
||||
|
||||
let mut raw_codes = Vec::new();
|
||||
let mut pagination = None;
|
||||
@@ -249,7 +240,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
}
|
||||
|
||||
async fn get_code_details(&self, code_id: ContractCodeId) -> Result<CodeDetails, NymdError> {
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/Code".parse().unwrap());
|
||||
let path = Some("/cosmwasm.wasm.v1beta1.Query/Code".parse().unwrap());
|
||||
|
||||
let req = QueryCodeRequest { code_id };
|
||||
|
||||
@@ -264,7 +255,11 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
}
|
||||
}
|
||||
async fn get_contracts(&self, code_id: ContractCodeId) -> Result<Vec<AccountId>, NymdError> {
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/ContractsByCode".parse().unwrap());
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1beta1.Query/ContractsByCode"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut raw_contracts = Vec::new();
|
||||
let mut pagination = None;
|
||||
@@ -295,7 +290,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
}
|
||||
|
||||
async fn get_contract(&self, address: &AccountId) -> Result<Contract, NymdError> {
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/ContractInfo".parse().unwrap());
|
||||
let path = Some("/cosmwasm.wasm.v1beta1.Query/ContractInfo".parse().unwrap());
|
||||
|
||||
let req = QueryContractInfoRequest {
|
||||
address: address.to_string(),
|
||||
@@ -320,7 +315,11 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
&self,
|
||||
address: &AccountId,
|
||||
) -> Result<Vec<ContractCodeHistoryEntry>, NymdError> {
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/ContractHistory".parse().unwrap());
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1beta1.Query/ContractHistory"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut raw_entries = Vec::new();
|
||||
let mut pagination = None;
|
||||
@@ -354,7 +353,11 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
address: &AccountId,
|
||||
query_data: Vec<u8>,
|
||||
) -> Result<Vec<u8>, NymdError> {
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/RawContractState".parse().unwrap());
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1beta1.Query/RawContractState"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let req = QueryRawContractStateRequest {
|
||||
address: address.to_string(),
|
||||
@@ -378,7 +381,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
let path = Some(
|
||||
"/cosmwasm.wasm.v1.Query/SmartContractState"
|
||||
"/cosmwasm.wasm.v1beta1.Query/SmartContractState"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -397,27 +400,4 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
|
||||
Ok(serde_json::from_slice(&res.data)?)
|
||||
}
|
||||
|
||||
// deprecation warning is due to the fact the protobuf files built were based on cosmos-sdk 0.44,
|
||||
// where they prefer using tx_bytes directly. However, in 0.42, which we are using at the time
|
||||
// of writing this, the option does not work
|
||||
#[allow(deprecated)]
|
||||
async fn query_simulate(
|
||||
&self,
|
||||
tx: Option<Tx>,
|
||||
tx_bytes: Vec<u8>,
|
||||
) -> Result<SimulateResponse, NymdError> {
|
||||
let path = Some("/cosmos.tx.v1beta1.Service/Simulate".parse().unwrap());
|
||||
|
||||
let req = SimulateRequest {
|
||||
tx: tx.map(Into::into),
|
||||
tx_bytes,
|
||||
};
|
||||
|
||||
let res = self
|
||||
.make_abci_query::<_, ProtoSimulateResponse>(path, req)
|
||||
.await?;
|
||||
|
||||
res.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ pub(crate) fn find_attribute<'a>(
|
||||
) -> Option<&'a cosmwasm_std::Attribute> {
|
||||
logs.iter()
|
||||
.flat_map(|log| log.events.iter())
|
||||
.find(|event| event.ty == event_type)?
|
||||
.find(|event| event.kind == event_type)?
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|attr| attr.key == attribute_key)
|
||||
@@ -61,7 +61,7 @@ mod tests {
|
||||
assert_eq!(parsed.len(), 1);
|
||||
assert_eq!(parsed[0].msg_index, 0);
|
||||
assert_eq!(parsed[0].events.len(), 1);
|
||||
assert_eq!(parsed[0].events[0].ty, "message");
|
||||
assert_eq!(parsed[0].events[0].kind, "message");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].value, "1");
|
||||
}
|
||||
@@ -76,12 +76,12 @@ mod tests {
|
||||
assert_eq!(parsed[2].msg_index, 2);
|
||||
|
||||
assert_eq!(parsed[0].events.len(), 1);
|
||||
assert_eq!(parsed[0].events[0].ty, "message");
|
||||
assert_eq!(parsed[0].events[0].kind, "message");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
|
||||
assert_eq!(parsed[0].events[0].attributes[3].value, "9");
|
||||
|
||||
assert_eq!(parsed[2].events.len(), 1);
|
||||
assert_eq!(parsed[2].events[0].ty, "message");
|
||||
assert_eq!(parsed[2].events[0].kind, "message");
|
||||
assert_eq!(parsed[2].events[0].attributes[2].key, "signer");
|
||||
assert_eq!(
|
||||
parsed[2].events[0].attributes[2].value,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use crate::nymd::GasPrice;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
|
||||
use std::convert::TryInto;
|
||||
|
||||
@@ -24,10 +23,9 @@ where
|
||||
pub fn connect_with_signer<U>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: Option<GasPrice>,
|
||||
) -> Result<signing_client::Client, NymdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
signing_client::Client::connect_with_signer(endpoint, signer, gas_price)
|
||||
signing_client::Client::connect_with_signer(endpoint, signer)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user