Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5bf36f1367 | |||
| 5da1d89501 | |||
| d7090ecb5a | |||
| 1a1a2a0355 |
+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
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
name: Continuous integration
|
||||
|
||||
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/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' }}
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
name: Clippy checks
|
||||
# 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' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
# COCONUT stuff
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Build all binaries with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all --features=coconut
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --features=coconut -- -D warnings
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,59 +0,0 @@
|
||||
name: CI for Network Explorer
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'explorer/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: explorer
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
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
|
||||
continue-on-error: true
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
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 }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Keybase - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files/messages
|
||||
- name: Keybase - Send Notification
|
||||
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 }}"
|
||||
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "ci-network-explorer"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/messages/entry_point_notifications.sh
|
||||
@@ -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,29 +0,0 @@
|
||||
name: Generate TS types
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "explorer/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "explorer/**"
|
||||
|
||||
jobs:
|
||||
nym-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
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Generate TS
|
||||
run: cd nym-wallet/src-tauri && cargo test
|
||||
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
|
||||
with:
|
||||
add: '["nym-wallet"]'
|
||||
message: "[ci skip] Generate TS types"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,77 +0,0 @@
|
||||
name: Webdriverio tests for nym wallet
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "nym-wallet/**"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nym-wallet
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: wallet tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Tauri dependencies
|
||||
run: >
|
||||
sudo apt-get update &&
|
||||
sudo apt-get install -y
|
||||
libgtk-3-dev
|
||||
libgtksourceview-3.0-dev
|
||||
webkit2gtk-4.0
|
||||
libappindicator3-dev
|
||||
webkit2gtk-driver
|
||||
xvfb
|
||||
|
||||
- name: Install minimal stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Install yarn for building application
|
||||
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
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
working-directory: nym-wallet/webdriver
|
||||
|
||||
- name: Remove existing user datafile
|
||||
uses: JesseTG/rm@v1.0.2
|
||||
with:
|
||||
path: nym-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: Install tauri-driver
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: tauri-driver
|
||||
|
||||
- name: Launch tests
|
||||
run: xvfb-run yarn test:runall
|
||||
working-directory: nym-wallet/webdriver
|
||||
@@ -0,0 +1,136 @@
|
||||
name: Publish Nym Wallet
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- tauri-wallet-*
|
||||
branches:
|
||||
- feature/gh-action-build-tauri-wallet
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: tauri-wallet
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-10.15, macos-11, windows-latest]
|
||||
outputs:
|
||||
PACKAGE_VERSION: ${{ steps.package-node-version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Tauri dependencies
|
||||
if: ${{ startsWith(matrix.os, 'ubuntu') }}
|
||||
run: >
|
||||
sudo apt-get update &&
|
||||
sudo apt-get install -y
|
||||
libgtk-3-dev
|
||||
libgtksourceview-3.0-dev
|
||||
webkit2gtk-4.0
|
||||
libappindicator3-dev
|
||||
webkit2gtk-driver
|
||||
xvfb
|
||||
|
||||
- name: Install minimal stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Read node from package.json
|
||||
uses: culshaw/read-package-node-version-actions@v1
|
||||
with:
|
||||
path: "${{github.workspace}}/tauri-wallet"
|
||||
id: package-node-version
|
||||
|
||||
- name: Install yarn for building application
|
||||
run: yarn install
|
||||
|
||||
- name: Build application (bundle)
|
||||
run: yarn run webpack:build
|
||||
|
||||
- name: Build application (tauri)
|
||||
run: yarn run tauri:build
|
||||
|
||||
# - name: Fake build
|
||||
# run: |
|
||||
# mkdir release-assets
|
||||
# cp package.json release-assets/${{ matrix.os }}-${{ steps.package-node-version.outputs.version }}.zip
|
||||
|
||||
- name: Compress (MacOS / Linux)
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
working-directory: tauri-wallet
|
||||
env:
|
||||
TARGET_FILE: ${{ matrix.os }}_v${{ steps.package-node-version.outputs.version }}.tar.gz
|
||||
run: |
|
||||
mkdir release-assets
|
||||
pushd target/release/bundle
|
||||
tar -cvzf ../../../release-assets/$TARGET_FILE .
|
||||
|
||||
- name: Compress (Windows)
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: '${{github.workspace}}/tauri-wallet/release-assets/${{ runner.os }}_v${{ steps.package-node-version.outputs.version }}.zip'
|
||||
path: "${{github.workspace}}/tauri-wallet/target/release/bundle"
|
||||
|
||||
- name: Upload release assets
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-assets-${{ matrix.os }}
|
||||
path: "${{github.workspace}}/tauri-wallet/release-assets/*"
|
||||
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ needs.build.outputs.PACKAGE_VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Download assets
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: download-assets
|
||||
- name: Display downloaded assets
|
||||
run: ls -R download-assets
|
||||
- name: Re-arrange downloaded assets
|
||||
run: |
|
||||
mkdir release-assets
|
||||
find download-assets -mindepth 2 -type f -exec mv -t ${{github.workspace}}/release-assets -i '{}' +
|
||||
rm -rf download-assets
|
||||
- name: Hash files
|
||||
id: file_hashes
|
||||
# Put the multi-line string in an env var and create a new action env var by wrapping the multiline value
|
||||
run: |
|
||||
cd release-assets
|
||||
ASSET_HASHES=$(find . -type f -exec sha256sum {} \;)
|
||||
echo "ASSET_HASHES<<EOF" >> $GITHUB_ENV
|
||||
echo "$ASSET_HASHES" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
- name: Display file hashes
|
||||
run: echo $ASSET_HASHES
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
# The pipe syntax needs to stay to escape the wildcard correctly
|
||||
artifacts: |
|
||||
release-assets/*
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: "nym-wallet-v${{env.PACKAGE_VERSION}}"
|
||||
commit: feature/gh-action-build-tauri-wallet
|
||||
draft: true
|
||||
name: "Nym Wallet v${{ env.PACKAGE_VERSION }}"
|
||||
body: |
|
||||
This is a build of the Nym Wallet from commit `${{ github.sha }}` [(browse commit)](https://github.com/nymtech/nym/tree/${{ github.sha }}).
|
||||
|
||||
Installers for each operating system have the following SHA256 hashes:
|
||||
|
||||
```
|
||||
${{ env.ASSET_HASHES }}
|
||||
```
|
||||
@@ -1,48 +0,0 @@
|
||||
name: Wasm Client
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'explorer/**'
|
||||
|
||||
jobs:
|
||||
wasm:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
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 --features=coconut
|
||||
|
||||
# for some reason this does not seem to work correctly, leave it for later, building is good enough for now
|
||||
# - uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: test
|
||||
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path clients/webassembly/Cargo.toml -- --check
|
||||
|
||||
# for some reason this does not seem to work correctly, leave it for later, building is good enough for now
|
||||
# - uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: clippy
|
||||
# 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
|
||||
@@ -24,15 +24,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
+49
-686
File diff suppressed because it is too large
Load Diff
+1
-4
@@ -25,12 +25,10 @@ members = [
|
||||
"common/config",
|
||||
"common/credentials",
|
||||
"common/crypto",
|
||||
"common/bandwidth-claim-contract",
|
||||
"common/mixnet-contract",
|
||||
"common/mixnode-common",
|
||||
"common/network-defaults",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
"common/nymcoconut",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
@@ -62,7 +60,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,4 +1,3 @@
|
||||
# Nym Desktop Client
|
||||
|
||||
The Nym Desktop Client communicates with the remote, decentralised nodes which make up the Nym system as a whole.
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
# Nym Desktop Client Code Examples
|
||||
|
||||
This directory contains example code for javascript, python, and rust. Please note that **none of these examples are production-ready**, and are included really just to show how the client interacts with the Nym system.
|
||||
|
||||
> Make sure that you have an instance of the `nym-client` set up and running locally on port 1977 before trying to run any of these examples.
|
||||
|
||||
## Go
|
||||
There are two examples here:
|
||||
* `binarysend`, which (as the name suggests) sends a binary file, and
|
||||
* `textsend` which sends a raw text file.
|
||||
|
||||
Both examples send these files over the Nym mixnet to the address of your running Nym client, logging information in your terminal window.
|
||||
|
||||
## Javascript
|
||||
The example included here starts a websocket server on port 8888, the ui of which can be used to send strings over the Nym mixnet to the address of your running Nym client.
|
||||
|
||||
### Prerequisites
|
||||
* Reasonably up to date `NodeJS` & `npm` (>= ~v12)
|
||||
|
||||
### Running it
|
||||
Run the following commands:
|
||||
|
||||
```
|
||||
# install dependencies
|
||||
npm install
|
||||
# start a webserver on port 8888
|
||||
npm start
|
||||
```
|
||||
|
||||
Then open your browser to `localhost:8888`.
|
||||
|
||||
## Python
|
||||
There are two examples here:
|
||||
* `binarysend`, which (as the name suggests) sends a binary file, and
|
||||
* `textsend` which sends a raw text file.
|
||||
|
||||
Both examples send these files over the Nym mixnet to the address of your running Nym client, logging information in your terminal window.
|
||||
|
||||
Make sure that you have an instance of the `nym-client` set up and running locally on port 1977 before trying to run either of the example scripts.
|
||||
|
||||
## Rust
|
||||
There are two examples here:
|
||||
* `binarysend`, which (as the name suggests) sends a binary file, and
|
||||
* `textsend` which sends a raw text file.
|
||||
|
||||
Both examples send these files over the Nym mixnet to the address of your running Nym client, logging information in your terminal window.
|
||||
|
||||
Make sure that you have an instance of the `nym-client` set up and running locally on port 1977 before trying to run either of the example scripts.
|
||||
Binary file not shown.
@@ -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
|
||||
)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
### Prerequisites
|
||||
|
||||
* Reasonably up to date Node + `npm`
|
||||
|
||||
### Running it
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start # starts a webserver on port 8888
|
||||
```
|
||||
+6987
-504
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"html-webpack-plugin": "^4.2.0",
|
||||
"postcss": "^8.4.5"
|
||||
"html-webpack-plugin": "^4.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam quis efficitur neque. Quisque aliquet vulputate ante, eget vehicula odio feugiat ac. Nulla ut mattis magna. Aenean tincidunt quis nulla eget eleifend. Cras in pretium sem. Nunc lorem metus, blandit sit amet egestas ut, feugiat quis tellus. Aenean tristique, enim a tincidunt condimentum, eros est blandit nunc, id viverra metus erat at nulla. Vivamus at tellus sodales, feugiat odio vel, laoreet neque. Vivamus posuere nulla ac sodales bibendum.
|
||||
|
||||
Vestibulum pulvinar nisi non ultricies egestas. Integer finibus ultrices justo vitae suscipit. Etiam interdum eu justo vel interdum. Morbi sagittis ac nisl quis consequat. Mauris dapibus ut risus ac facilisis. Pellentesque non tortor feugiat, consectetur arcu vel, ullamcorper sapien. Proin sodales purus non orci bibendum, sit amet ultrices justo ullamcorper. Nullam ac risus ac justo ultricies efficitur auctor nec arcu. Etiam sed finibus felis. Suspendisse potenti. Phasellus malesuada velit ac ullamcorper egestas. Sed elementum diam ut est gravida ultricies.
|
||||
|
||||
Pellentesque sed metus massa. Cras imperdiet lacus sit amet dolor aliquam, luctus posuere justo hendrerit. Morbi augue ex, gravida a metus sed, scelerisque euismod lacus. Nam consequat sapien ac pellentesque sagittis. Morbi a ultrices massa, vel aliquet ex. Maecenas ac sem diam. Nunc sed erat et ipsum volutpat auctor. Etiam elit felis, commodo vitae ipsum ac, fermentum lobortis arcu. Aliquam eu tempus enim. Curabitur vulputate imperdiet aliquam. Morbi iaculis rhoncus risus at malesuada. Donec accumsan feugiat ligula ut facilisis. Nunc porttitor sit amet est eget malesuada. Sed sed consectetur augue, non dapibus orci. Mauris aliquam pellentesque quam, sit amet pellentesque velit cursus vitae. Morbi sit amet molestie risus.
|
||||
|
||||
Nam gravida non ligula a egestas. Fusce sodales, purus id rhoncus mattis, purus est vehicula urna, vel finibus augue velit et est. Donec dictum erat eleifend lobortis iaculis. Praesent id venenatis ante. Donec feugiat, ipsum eget porttitor pulvinar, nisl odio posuere lorem, ut placerat elit nulla a ligula. Suspendisse nec nibh tincidunt, sollicitudin mi a, volutpat ligula. In maximus quam lacus, eget semper dolor sagittis sit amet.
|
||||
|
||||
In vitae hendrerit est, quis facilisis dui. In eu ante enim. Nullam hendrerit odio sit amet odio tincidunt eleifend. Aliquam erat volutpat. Curabitur commodo, purus pharetra lobortis rhoncus, tortor massa imperdiet nisl, vel dignissim tortor sem at orci. Aliquam maximus lobortis lacus, eu porttitor purus dapibus ut. Praesent at dapibus felis, efficitur blandit tortor. In hac habitasse platea dictumst. Aenean ultrices, nisl a pretium sagittis, tellus sapien mollis erat, eu consectetur erat mauris sed libero. Duis feugiat dapibus mi, vel ornare velit vehicula mattis. Ut suscipit pharetra leo et sollicitudin.
|
||||
@@ -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,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use crate::websocket;
|
||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use client_core::client::inbound_messages::{
|
||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||
@@ -22,7 +24,6 @@ use client_core::client::topology_control::{
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
use gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
MixnetMessageSender,
|
||||
@@ -32,9 +33,12 @@ use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use crate::websocket;
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
|
||||
pub(crate) mod config;
|
||||
|
||||
@@ -43,6 +47,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 +71,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 +97,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 +112,7 @@ impl NymClient {
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
)
|
||||
.start();
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
fn start_real_traffic_controller(
|
||||
@@ -121,6 +134,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 +147,7 @@ impl NymClient {
|
||||
topology_accessor,
|
||||
reply_key_storage,
|
||||
)
|
||||
.start();
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -148,10 +165,35 @@ 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 bandwidth_credential = credentials::bandwidth::obtain_signature(
|
||||
&self.key_manager.identity_keypair().public_key().to_bytes(),
|
||||
&self.config.get_base().get_validator_api_endpoints(),
|
||||
)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
// the above would presumably be loaded from a file
|
||||
|
||||
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
|
||||
prepare_for_spending(
|
||||
&self.key_manager.identity_keypair().public_key().to_bytes(),
|
||||
&bandwidth_credential,
|
||||
&verification_key,
|
||||
)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
fn start_gateway_client(
|
||||
&mut self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
@@ -168,44 +210,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 +249,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 +263,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 +276,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 +289,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 +337,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 +351,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 +383,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,23 +1,15 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::commands::override_config;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
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,
|
||||
};
|
||||
#[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;
|
||||
@@ -29,17 +21,8 @@ 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")
|
||||
@@ -53,9 +36,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("validators")
|
||||
.long("validators")
|
||||
.help("Comma separated list of rest endpoints of the validators")
|
||||
.takes_value(true),
|
||||
.long("validators")
|
||||
.help("Comma separated list of rest endpoints of the validators")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(Arg::with_name("disable-socket")
|
||||
.long("disable-socket")
|
||||
@@ -71,61 +54,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
|
||||
// need to get bandwidth credential for registration
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(validators)
|
||||
.await
|
||||
.expect("could not obtain aggregate verification key of validators");
|
||||
|
||||
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
|
||||
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
|
||||
serial_number: params.random_scalar(),
|
||||
binding_number: params.random_scalar(),
|
||||
voucher_value: hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
|
||||
voucher_info: hash_to_scalar(String::from("BandwidthVoucher").as_bytes()),
|
||||
};
|
||||
|
||||
let bandwidth_credential =
|
||||
obtain_signature(¶ms, &bandwidth_credential_attributes, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
prepare_for_spending(
|
||||
raw_identity,
|
||||
&bandwidth_credential,
|
||||
&bandwidth_credential_attributes,
|
||||
&verification_key,
|
||||
)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
async fn register_with_gateway(
|
||||
@@ -219,7 +148,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 +166,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 +179,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,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::socks::{
|
||||
authentication::{AuthenticationMethods, Authenticator, User},
|
||||
server::SphinxSocksServer,
|
||||
};
|
||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use client_core::client::inbound_messages::{
|
||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||
@@ -20,7 +25,6 @@ use client_core::client::topology_control::{
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
use gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
MixnetMessageSender,
|
||||
@@ -28,12 +32,12 @@ use gateway_client::{
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::socks::{
|
||||
authentication::{AuthenticationMethods, Authenticator, User},
|
||||
server::SphinxSocksServer,
|
||||
};
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
|
||||
pub(crate) mod config;
|
||||
|
||||
@@ -42,6 +46,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 +61,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 +85,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 +100,7 @@ impl NymClient {
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
)
|
||||
.start();
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
fn start_real_traffic_controller(
|
||||
@@ -109,6 +122,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 +135,7 @@ impl NymClient {
|
||||
topology_accessor,
|
||||
reply_key_storage,
|
||||
)
|
||||
.start();
|
||||
.start(self.runtime.handle());
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -136,10 +153,35 @@ 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 bandwidth_credential = credentials::bandwidth::obtain_signature(
|
||||
&self.key_manager.identity_keypair().public_key().to_bytes(),
|
||||
&self.config.get_base().get_validator_api_endpoints(),
|
||||
)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
// the above would presumably be loaded from a file
|
||||
|
||||
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
|
||||
prepare_for_spending(
|
||||
&self.key_manager.identity_keypair().public_key().to_bytes(),
|
||||
&bandwidth_credential,
|
||||
&verification_key,
|
||||
)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
fn start_gateway_client(
|
||||
&mut self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
@@ -156,44 +198,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 +237,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 +251,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 +264,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 +283,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 +302,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 +334,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,23 +1,15 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::commands::override_config;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
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,
|
||||
};
|
||||
#[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};
|
||||
@@ -27,17 +19,8 @@ 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")
|
||||
@@ -57,9 +40,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("validators")
|
||||
.long("validators")
|
||||
.help("Comma separated list of rest endpoints of the validators")
|
||||
.takes_value(true),
|
||||
.long("validators")
|
||||
.help("Comma separated list of rest endpoints of the validators")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
@@ -71,61 +54,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
|
||||
// need to get bandwidth credential for registration
|
||||
#[cfg(feature = "coconut")]
|
||||
async fn _prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
|
||||
let verification_key = obtain_aggregate_verification_key(validators)
|
||||
.await
|
||||
.expect("could not obtain aggregate verification key of validators");
|
||||
|
||||
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
|
||||
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
|
||||
serial_number: params.random_scalar(),
|
||||
binding_number: params.random_scalar(),
|
||||
voucher_value: hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
|
||||
voucher_info: hash_to_scalar("BandwidthVoucher"),
|
||||
};
|
||||
|
||||
let bandwidth_credential =
|
||||
obtain_signature(¶ms, &bandwidth_credential_attributes, validators)
|
||||
.await
|
||||
.expect("could not obtain bandwidth credential");
|
||||
|
||||
prepare_for_spending(
|
||||
raw_identity,
|
||||
&bandwidth_credential,
|
||||
&bandwidth_credential_attributes,
|
||||
&verification_key,
|
||||
)
|
||||
.expect("could not prepare out bandwidth credential for spending")
|
||||
}
|
||||
|
||||
async fn register_with_gateway(
|
||||
@@ -219,7 +148,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 +167,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 +180,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")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,24 +3,21 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
use url::Url;
|
||||
|
||||
use coconut_interface::{
|
||||
self, hash_to_scalar, Attribute, Credential, Parameters, Signature, Theta, VerificationKey,
|
||||
};
|
||||
use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use url::Url;
|
||||
|
||||
struct State {
|
||||
signatures: Vec<Signature>,
|
||||
n_attributes: u32,
|
||||
params: Parameters,
|
||||
serial_number: Attribute,
|
||||
binding_number: Attribute,
|
||||
voucher_value: Attribute,
|
||||
voucher_info: Attribute,
|
||||
public_attributes_bytes: Vec<Vec<u8>>,
|
||||
public_attributes: Vec<Attribute>,
|
||||
private_attributes: Vec<Attribute>,
|
||||
aggregated_verification_key: Option<VerificationKey>,
|
||||
}
|
||||
|
||||
@@ -40,10 +37,9 @@ impl State {
|
||||
signatures: Vec::new(),
|
||||
n_attributes,
|
||||
params,
|
||||
serial_number: private_attributes[0],
|
||||
binding_number: private_attributes[1],
|
||||
voucher_value: public_attributes[0],
|
||||
voucher_info: public_attributes[1],
|
||||
public_attributes_bytes,
|
||||
public_attributes,
|
||||
private_attributes,
|
||||
aggregated_verification_key: None,
|
||||
}
|
||||
}
|
||||
@@ -67,8 +63,8 @@ async fn randomise_credential(
|
||||
) -> Result<Vec<Signature>, String> {
|
||||
let mut state = state.write().await;
|
||||
let signature = state.signatures.remove(idx);
|
||||
let (new_signature, _) = signature.randomise(&state.params);
|
||||
state.signatures.insert(idx, new_signature);
|
||||
let new = signature.randomise(&state.params);
|
||||
state.signatures.insert(idx, new);
|
||||
Ok(state.signatures.clone())
|
||||
}
|
||||
|
||||
@@ -121,15 +117,14 @@ async fn prove_credential(
|
||||
let state = state.read().await;
|
||||
|
||||
if let Some(signature) = state.signatures.get(idx) {
|
||||
match coconut_interface::prove_bandwidth_credential(
|
||||
match coconut_interface::prove_credential(
|
||||
&state.params,
|
||||
&verification_key,
|
||||
signature,
|
||||
state.serial_number,
|
||||
state.binding_number,
|
||||
&state.private_attributes,
|
||||
) {
|
||||
Ok(theta) => Ok(theta),
|
||||
Err(e) => Err(format!("{:?}", e)),
|
||||
Err(e) => Err(format!("{}", e)),
|
||||
}
|
||||
} else {
|
||||
Err("Got invalid Signature idx".to_string())
|
||||
@@ -149,15 +144,10 @@ async fn verify_credential(
|
||||
|
||||
let state = state.read().await;
|
||||
|
||||
let public_attributes_bytes = vec![
|
||||
state.voucher_value.to_bytes().to_vec(),
|
||||
state.voucher_info.to_bytes().to_vec(),
|
||||
];
|
||||
|
||||
let credential = Credential::new(
|
||||
state.n_attributes,
|
||||
theta,
|
||||
public_attributes_bytes,
|
||||
state.public_attributes_bytes.clone(),
|
||||
state
|
||||
.signatures
|
||||
.get(idx)
|
||||
@@ -174,13 +164,11 @@ async fn get_credential(
|
||||
) -> Result<Vec<Signature>, String> {
|
||||
let guard = state.read().await;
|
||||
let parsed_urls = parse_url_validators(&validator_urls)?;
|
||||
let public_attributes = vec![guard.voucher_value, guard.voucher_info];
|
||||
let private_attributes = vec![guard.serial_number, guard.binding_number];
|
||||
|
||||
let signature = obtain_aggregate_signature(
|
||||
&guard.params,
|
||||
&public_attributes,
|
||||
&private_attributes,
|
||||
&guard.public_attributes,
|
||||
&guard.private_attributes,
|
||||
&parsed_urls,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Generated
+3848
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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",
|
||||
@@ -9,8 +9,7 @@
|
||||
"build": "tsc",
|
||||
"test": "ts-mocha tests/**/*.test.ts",
|
||||
"coverage": "nyc npm test",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"lint": "eslint \"**/*.ts\"",
|
||||
"docs": "typedoc --out docs src/index.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
@@ -24,33 +23,22 @@
|
||||
"@types/chai": "^4.2.15",
|
||||
"@types/expect": "^24.3.0",
|
||||
"@types/mocha": "^8.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.7.0",
|
||||
"@typescript-eslint/parser": "^5.7.0",
|
||||
"@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-mocha": "^10.0.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"mocha": "^8.2.1",
|
||||
"moq.ts": "^7.2.0",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.5.1",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"typedoc": "^0.20.27",
|
||||
"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"
|
||||
"@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,14 +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 {
|
||||
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
|
||||
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
|
||||
}
|
||||
|
||||
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: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2422
-2936
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"
|
||||
|
||||
+6569
-12
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,6 @@
|
||||
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
use gateway_client::GatewayClient;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
@@ -18,6 +17,11 @@ use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use wasm_utils::{console_log, console_warn};
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
use coconut_interface::Credential;
|
||||
#[cfg(feature = "coconut")]
|
||||
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
|
||||
|
||||
pub(crate) mod received_processor;
|
||||
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
|
||||
@@ -27,7 +31,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 +76,6 @@ impl NymClient {
|
||||
|
||||
on_message: None,
|
||||
on_gateway_connect: None,
|
||||
testnet_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,11 +88,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 +103,35 @@ 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 bandwidth_credential =
|
||||
credentials::bandwidth::obtain_signature(identity_bytes, 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, &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 +147,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");
|
||||
|
||||
@@ -262,7 +275,7 @@ impl NymClient {
|
||||
let validator_client = validator_client::ApiClient::new(self.validator_server.clone());
|
||||
|
||||
let mixnodes = match validator_client.get_cached_active_mixnodes().await {
|
||||
Err(err) => panic!("{:?}", err),
|
||||
Err(err) => panic!("{}", err),
|
||||
Ok(mixes) => mixes,
|
||||
};
|
||||
|
||||
|
||||
@@ -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,6 +139,7 @@ impl GatewayClient {
|
||||
self.gateway_identity
|
||||
}
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub fn remaining_bandwidth(&self) -> i64 {
|
||||
self.bandwidth_remaining
|
||||
}
|
||||
@@ -213,7 +208,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 +234,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 +451,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 +478,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 +507,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 +522,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 +590,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 +629,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 +660,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)
|
||||
}
|
||||
|
||||
@@ -1,100 +1,37 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::bank::MsgSend;
|
||||
use cosmrs::distribution::MsgWithdrawDelegatorReward;
|
||||
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
|
||||
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
|
||||
use cosmrs::tx::{self, Msg, SignDoc, SignerInfo};
|
||||
use cosmrs::{cosmwasm, rpc, AccountId, Any, Coin, Tx};
|
||||
use log::debug;
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
use crate::nymd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse};
|
||||
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
|
||||
use crate::nymd::cosmwasm_client::types::*;
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER};
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use crate::nymd::{CosmosCoin, GasPrice};
|
||||
|
||||
// we need to have **a** valid secp256k1 signature for simulation purposes.
|
||||
// it doesn't matter what it is as long as it parses correctly
|
||||
const DUMMY_SECP256K1_SIGNATURE: &[u8] = &[
|
||||
54, 167, 169, 61, 100, 173, 231, 87, 1, 113, 179, 49, 102, 141, 67, 22, 170, 153, 52, 88, 178,
|
||||
159, 200, 11, 37, 138, 76, 221, 187, 70, 104, 123, 98, 216, 190, 249, 149, 81, 1, 158, 0, 220,
|
||||
32, 147, 101, 60, 64, 77, 44, 83, 221, 119, 170, 124, 109, 177, 73, 116, 46, 57, 102, 181, 98,
|
||||
91,
|
||||
];
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::bank::MsgSend;
|
||||
use cosmrs::distribution::MsgWithdrawDelegatorReward;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
|
||||
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
|
||||
use cosmrs::tx::{Fee, Msg, SignDoc, SignerInfo};
|
||||
use cosmrs::{cosmwasm, rpc, tx, AccountId, Any, Coin};
|
||||
use log::debug;
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use sha2::Sha256;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[async_trait]
|
||||
pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
fn signer(&self) -> &DirectSecp256k1HdWallet;
|
||||
|
||||
fn gas_price(&self) -> &GasPrice;
|
||||
|
||||
fn signer_public_key(&self, signer_address: &AccountId) -> Option<tx::SignerPublicKey> {
|
||||
let signer_accounts = self.signer().try_derive_accounts().ok()?;
|
||||
let account_from_signer = signer_accounts
|
||||
.iter()
|
||||
.find(|account| &account.address == signer_address)?;
|
||||
let public_key = account_from_signer.public_key;
|
||||
Some(public_key.into())
|
||||
}
|
||||
|
||||
async fn simulate(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<SimulateResponse, NymdError> {
|
||||
let public_key = self.signer_public_key(signer_address);
|
||||
let sequence_response = self.get_sequence(signer_address).await?;
|
||||
|
||||
let partial_tx = Tx {
|
||||
body: tx::Body {
|
||||
messages,
|
||||
memo: memo.into(),
|
||||
timeout_height: 0u32.into(),
|
||||
extension_options: vec![],
|
||||
non_critical_extension_options: vec![],
|
||||
},
|
||||
auth_info: tx::AuthInfo {
|
||||
signer_infos: vec![tx::SignerInfo {
|
||||
public_key,
|
||||
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
|
||||
mode: SignMode::Unspecified,
|
||||
}),
|
||||
sequence: sequence_response.sequence,
|
||||
}],
|
||||
fee: tx::Fee::from_amount_and_gas(
|
||||
CosmosCoin {
|
||||
denom: "".parse().unwrap(),
|
||||
amount: 0u64.into(),
|
||||
},
|
||||
0,
|
||||
),
|
||||
},
|
||||
signatures: vec![DUMMY_SECP256K1_SIGNATURE.try_into().unwrap()],
|
||||
};
|
||||
|
||||
self.query_simulate(Some(partial_tx), Vec::new()).await
|
||||
}
|
||||
|
||||
async fn upload(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
wasm_code: Vec<u8>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
mut meta: Option<UploadMeta>,
|
||||
) -> Result<UploadResult, NymdError> {
|
||||
let compressed = compress_wasm_code(&wasm_code)?;
|
||||
let compressed_size = compressed.len();
|
||||
@@ -105,6 +42,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
let upload_msg = cosmwasm::MsgStoreCode {
|
||||
sender: sender_address.clone(),
|
||||
wasm_byte_code: compressed,
|
||||
source: meta
|
||||
.as_mut()
|
||||
.map(|meta| meta.source.take())
|
||||
.unwrap_or_default(),
|
||||
builder: meta
|
||||
.as_mut()
|
||||
.map(|meta| meta.builder.take())
|
||||
.unwrap_or_default(),
|
||||
instantiate_permission: Default::default(),
|
||||
}
|
||||
.to_any()
|
||||
@@ -116,13 +61,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.check_response()?;
|
||||
|
||||
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
|
||||
// TODO: should those strings be extracted into some constants?
|
||||
// the reason I think unwrap here is fine is that if the transaction succeeded and those
|
||||
// fields do not exist or code_id is not a number, there's no way we can recover, we're probably connected
|
||||
// to wrong validator or something
|
||||
let code_id = logs::find_attribute(&logs, "store_code", "code_id")
|
||||
let code_id = logs::find_attribute(&logs, "message", "code_id")
|
||||
.unwrap()
|
||||
.value
|
||||
.parse()
|
||||
@@ -136,7 +80,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
code_id,
|
||||
logs,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -168,7 +111,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
// now this is a weird one. the protobuf files say this field is optional,
|
||||
// but if you omit it, the initialisation will fail CheckTx
|
||||
label: Some(label),
|
||||
msg: serde_json::to_vec(msg)?,
|
||||
init_msg: serde_json::to_vec(msg)?,
|
||||
funds: options.map(|options| options.funds).unwrap_or_default(),
|
||||
}
|
||||
.to_any()
|
||||
@@ -180,13 +123,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.check_response()?;
|
||||
|
||||
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
|
||||
// TODO: should those strings be extracted into some constants?
|
||||
// the reason I think unwrap here is fine is that if the transaction succeeded and those
|
||||
// fields do not exist or address is malformed, there's no way we can recover, we're probably connected
|
||||
// to wrong validator or something
|
||||
let contract_address = logs::find_attribute(&logs, "instantiate", "_contract_address")
|
||||
let contract_address = logs::find_attribute(&logs, "message", "contract_address")
|
||||
.unwrap()
|
||||
.value
|
||||
.parse()
|
||||
@@ -196,7 +138,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
contract_address,
|
||||
logs,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -221,12 +162,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
|
||||
Ok(ChangeAdminResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -249,12 +187,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
|
||||
Ok(ChangeAdminResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -274,7 +209,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
sender: sender_address.clone(),
|
||||
contract: contract_address.clone(),
|
||||
code_id,
|
||||
msg: serde_json::to_vec(msg)?,
|
||||
migrate_msg: serde_json::to_vec(msg)?,
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
|
||||
@@ -284,12 +219,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
|
||||
Ok(MigrateResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -319,12 +251,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -359,12 +288,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
debug!(
|
||||
"gas wanted: {:?}, gas used: {:?}",
|
||||
tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used
|
||||
);
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -385,36 +316,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
}
|
||||
|
||||
async fn send_tokens_multiple<I>(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
msgs: I,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError>
|
||||
where
|
||||
I: IntoIterator<Item = (AccountId, Vec<Coin>)> + Send,
|
||||
{
|
||||
let messages = msgs
|
||||
.into_iter()
|
||||
.map(|(to_address, amount)| {
|
||||
MsgSend {
|
||||
from_address: sender_address.clone(),
|
||||
to_address,
|
||||
amount,
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
self.sign_and_broadcast_commit(sender_address, messages, fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
.await
|
||||
}
|
||||
|
||||
async fn delegate_tokens(
|
||||
@@ -434,8 +336,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
.await
|
||||
}
|
||||
|
||||
async fn undelegate_tokens(
|
||||
@@ -455,8 +356,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
.await
|
||||
}
|
||||
|
||||
async fn withdraw_rewards(
|
||||
@@ -474,45 +374,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
}
|
||||
|
||||
// in this particular case we cannot generalise the argument to `&str` due to lifetime constraints
|
||||
#[allow(clippy::ptr_arg)]
|
||||
async fn determine_transaction_fee(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: &[Any],
|
||||
fee: Fee,
|
||||
memo: &String,
|
||||
) -> Result<tx::Fee, NymdError> {
|
||||
let fee = match fee {
|
||||
Fee::Manual(fee) => fee,
|
||||
Fee::Auto(multiplier) => {
|
||||
debug!("Trying to simulate gas costs...");
|
||||
// from what I've seen in manual testing, gas estimation does not exist if transaction
|
||||
// fails to get executed (for example if you send 'BondMixnode" with invalid signature)
|
||||
let gas_estimation = self
|
||||
.simulate(signer_address, messages.to_vec(), memo.clone())
|
||||
.await?
|
||||
.gas_info
|
||||
.ok_or(NymdError::GasEstimationFailure)?
|
||||
.gas_used;
|
||||
|
||||
let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER);
|
||||
let gas = ((gas_estimation.value() as f32 * multiplier) as u64).into();
|
||||
|
||||
debug!("Gas estimation: {}", gas_estimation);
|
||||
debug!("Multiplying the estimation by {}", multiplier);
|
||||
debug!("Final gas limit used: {}", gas);
|
||||
|
||||
let fee = self.gas_price() * gas;
|
||||
tx::Fee::from_amount_and_gas(fee, gas)
|
||||
}
|
||||
};
|
||||
debug!("Fee used for the transaction: {:?}", fee);
|
||||
Ok(fee)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Broadcast a transaction, returning immediately.
|
||||
@@ -523,10 +385,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_async::Response, NymdError> {
|
||||
let memo = memo.into();
|
||||
let fee = self
|
||||
.determine_transaction_fee(signer_address, &messages, fee, &memo)
|
||||
.await?;
|
||||
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
|
||||
let tx_bytes = tx_raw
|
||||
.to_bytes()
|
||||
@@ -543,10 +401,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_sync::Response, NymdError> {
|
||||
let memo = memo.into();
|
||||
let fee = self
|
||||
.determine_transaction_fee(signer_address, &messages, fee, &memo)
|
||||
.await?;
|
||||
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
|
||||
let tx_bytes = tx_raw
|
||||
.to_bytes()
|
||||
@@ -563,11 +417,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError> {
|
||||
let memo = memo.into();
|
||||
let fee = self
|
||||
.determine_transaction_fee(signer_address, &messages, fee, &memo)
|
||||
.await?;
|
||||
|
||||
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
|
||||
let tx_bytes = tx_raw
|
||||
.to_bytes()
|
||||
@@ -580,7 +429,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
fee: tx::Fee,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
signer_data: SignerData,
|
||||
) -> Result<tx::Raw, NymdError> {
|
||||
@@ -618,7 +467,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
fee: tx::Fee,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<tx::Raw, NymdError> {
|
||||
// TODO: Future optimisation: rather than grabbing current account_number and sequence
|
||||
@@ -636,28 +485,22 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
rpc_client: HttpClient,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: GasPrice,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn connect_with_signer<U>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: Option<GasPrice>,
|
||||
) -> Result<Self, NymdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let rpc_client = HttpClient::new(endpoint)?;
|
||||
Ok(Client {
|
||||
rpc_client,
|
||||
signer,
|
||||
gas_price: gas_price.unwrap_or_default(),
|
||||
})
|
||||
Ok(Client { rpc_client, signer })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -679,8 +522,4 @@ impl SigningCosmWasmClient for Client {
|
||||
fn signer(&self) -> &DirectSecp256k1HdWallet {
|
||||
&self.signer
|
||||
}
|
||||
|
||||
fn gas_price(&self) -> &GasPrice {
|
||||
&self.gas_price
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,15 @@ use crate::nymd::cosmwasm_client::logs::Log;
|
||||
use crate::nymd::error::NymdError;
|
||||
use cosmrs::crypto::PublicKey;
|
||||
use cosmrs::proto::cosmos::auth::v1beta1::BaseAccount;
|
||||
use cosmrs::proto::cosmos::base::abci::v1beta1::{
|
||||
GasInfo as ProtoGasInfo, Result as ProtoAbciResult,
|
||||
};
|
||||
use cosmrs::proto::cosmos::tx::v1beta1::SimulateResponse as ProtoSimulateResponse;
|
||||
use cosmrs::proto::cosmwasm::wasm::v1::{
|
||||
use cosmrs::proto::cosmwasm::wasm::v1beta1::{
|
||||
CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry,
|
||||
ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo,
|
||||
};
|
||||
use cosmrs::tendermint::{abci, chain};
|
||||
use cosmrs::tx::{AccountNumber, Gas, SequenceNumber};
|
||||
use cosmrs::tendermint::chain;
|
||||
use cosmrs::tx::{AccountNumber, SequenceNumber};
|
||||
use cosmrs::{tx, AccountId, Coin};
|
||||
use serde::Serialize;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub type ContractCodeId = u64;
|
||||
|
||||
@@ -74,6 +70,18 @@ pub struct Code {
|
||||
|
||||
/// sha256 hash of the code stored
|
||||
pub data_hash: Vec<u8>,
|
||||
|
||||
/// An URL to a .tar.gz archive of the source code of the contract,
|
||||
/// which can be used to reproducibly build the Wasm bytecode.
|
||||
///
|
||||
/// @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
pub source: Option<String>,
|
||||
|
||||
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
|
||||
///
|
||||
/// @example ```cosmwasm/rust-optimizer:0.8.0```
|
||||
/// @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
pub builder: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<CodeInfoResponse> for Code {
|
||||
@@ -84,16 +92,31 @@ impl TryFrom<CodeInfoResponse> for Code {
|
||||
code_id,
|
||||
creator,
|
||||
data_hash,
|
||||
source,
|
||||
builder,
|
||||
} = value;
|
||||
|
||||
let creator = creator
|
||||
.parse()
|
||||
.map_err(|_| NymdError::MalformedAccountAddress(creator))?;
|
||||
|
||||
let source = if source.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(source)
|
||||
};
|
||||
let builder = if builder.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(builder)
|
||||
};
|
||||
|
||||
Ok(Code {
|
||||
code_id,
|
||||
creator,
|
||||
data_hash,
|
||||
source,
|
||||
builder,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -219,101 +242,6 @@ impl TryFrom<ProtoContractCodeHistoryEntry> for ContractCodeHistoryEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GasInfo {
|
||||
/// GasWanted is the maximum units of work we allow this tx to perform.
|
||||
pub gas_wanted: Gas,
|
||||
|
||||
/// GasUsed is the amount of gas actually consumed.
|
||||
pub gas_used: Gas,
|
||||
}
|
||||
|
||||
impl From<ProtoGasInfo> for GasInfo {
|
||||
fn from(value: ProtoGasInfo) -> Self {
|
||||
GasInfo {
|
||||
gas_wanted: value.gas_wanted.into(),
|
||||
gas_used: value.gas_used.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GasInfo {
|
||||
pub fn new(gas_wanted: Gas, gas_used: Gas) -> Self {
|
||||
GasInfo {
|
||||
gas_wanted,
|
||||
gas_used,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AbciResult {
|
||||
/// Data is any data returned from message or handler execution. It MUST be
|
||||
/// length prefixed in order to separate data from multiple message executions.
|
||||
pub data: Vec<u8>,
|
||||
|
||||
/// Log contains the log information from message or handler execution.
|
||||
// todo: try to parse into Log?
|
||||
pub log: String,
|
||||
|
||||
/// Events contains a slice of Event objects that were emitted during message
|
||||
/// or handler execution.
|
||||
pub events: Vec<abci::Event>,
|
||||
}
|
||||
|
||||
impl TryFrom<ProtoAbciResult> for AbciResult {
|
||||
type Error = NymdError;
|
||||
|
||||
fn try_from(value: ProtoAbciResult) -> Result<Self, Self::Error> {
|
||||
let mut events = Vec::with_capacity(value.events.len());
|
||||
|
||||
for proto_event in value.events.into_iter() {
|
||||
let type_str = proto_event.r#type;
|
||||
|
||||
let mut attributes = Vec::with_capacity(proto_event.attributes.len());
|
||||
for proto_attribute in proto_event.attributes.into_iter() {
|
||||
let stringified_ked = String::from_utf8(proto_attribute.key)
|
||||
.map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?;
|
||||
let stringified_value = String::from_utf8(proto_attribute.value)
|
||||
.map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?;
|
||||
|
||||
attributes.push(abci::tag::Tag {
|
||||
key: stringified_ked.parse().unwrap(),
|
||||
value: stringified_value.parse().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
events.push(abci::Event {
|
||||
type_str,
|
||||
attributes,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(AbciResult {
|
||||
data: value.data,
|
||||
log: value.log,
|
||||
events,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SimulateResponse {
|
||||
pub gas_info: Option<GasInfo>,
|
||||
pub result: Option<AbciResult>,
|
||||
}
|
||||
|
||||
impl TryFrom<ProtoSimulateResponse> for SimulateResponse {
|
||||
type Error = NymdError;
|
||||
|
||||
fn try_from(value: ProtoSimulateResponse) -> Result<Self, Self::Error> {
|
||||
Ok(SimulateResponse {
|
||||
gas_info: value.gas_info.map(|gas_info| gas_info.into()),
|
||||
result: value.result.map(|result| result.try_into()).transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ##############################################################################
|
||||
// types specific to the signing client (perhaps they should go to separate file)
|
||||
// ##############################################################################
|
||||
@@ -326,6 +254,21 @@ pub struct SignerData {
|
||||
pub chain_id: chain::Id,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UploadMeta {
|
||||
/// An URL to a .tar.gz archive of the source code of the contract,
|
||||
/// which can be used to reproducibly build the Wasm bytecode.
|
||||
///
|
||||
/// @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
pub source: Option<String>,
|
||||
|
||||
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
|
||||
///
|
||||
/// @example ```cosmwasm/rust-optimizer:0.8.0```
|
||||
/// @see https://github.com/CosmWasm/cosmwasm-verify
|
||||
pub builder: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UploadResult {
|
||||
/// Size of the original wasm code in bytes
|
||||
@@ -347,8 +290,6 @@ pub struct UploadResult {
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -375,8 +316,6 @@ pub struct InstantiateResult {
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -385,8 +324,6 @@ pub struct ChangeAdminResult {
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -395,8 +332,6 @@ pub struct MigrateResult {
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -405,6 +340,4 @@ pub struct ExecuteResult {
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: tx::Hash,
|
||||
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user