diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3ae81652a4..229060b381 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,14 +9,24 @@ on:
- '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' }}
- strategy:
- matrix:
- rust: [stable, beta, nightly]
- os: [ubuntu-latest, macos-latest, windows-latest]
-
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
@@ -51,6 +61,13 @@ jobs:
command: fmt
args: --all -- --check
+ - uses: actions-rs/clippy-check@v1
+ name: Clippy checks
+# if: matrix.os == 'ubuntu-latest' && matrix.rust == 'stable'
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ args: --all-features
+
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
@@ -65,78 +82,21 @@ jobs:
with:
command: clean
- # BUILD
- - name: Build gateway with coconut feature
+ - name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
- args: --bin nym-gateway --features=coconut
+ args: --all --features=coconut
- - name: Build native client with coconut feature
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --bin nym-client --features=coconut
-
- - name: Build socks5 client with coconut feature
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --bin nym-socks5-client --features=coconut
-
- - name: Build validator-api with coconut feature
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --bin nym-validator-api --features=coconut
-
-# TEST
- - name: Test gateway with coconut feature
+ - name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
- args: --bin nym-gateway --features=coconut
+ args: --all --features=coconut
- - name: Test native client with coconut feature
- uses: actions-rs/cargo@v1
- with:
- command: test
- args: --bin nym-client --features=coconut
-
- - name: Test socks5 client with coconut feature
- uses: actions-rs/cargo@v1
- with:
- command: test
- args: --bin nym-socks5-client --features=coconut
-
- - name: Test validator-api with coconut feature
- uses: actions-rs/cargo@v1
- with:
- command: test
- args: --bin nym-validator-api --features=coconut
-
-# CLIPPY
-
- - name: Run clippy on gateway with coconut feature
+ - name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
+ if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
- args: --bin nym-gateway --features=coconut -- -D warnings
-
- - name: Run clippy on native client with coconut feature
- uses: actions-rs/cargo@v1
- with:
- command: clippy
- args: --bin nym-client --features=coconut -- -D warnings
-
- - name: Run clippy on socks5 client with coconut feature
- uses: actions-rs/cargo@v1
- with:
- command: clippy
- args: --bin nym-socks5-client --features=coconut -- -D warnings
-
- - name: Run clippy on validator-api with coconut feature
- uses: actions-rs/cargo@v1
- with:
- command: clippy
- args: --bin nym-validator-api --features=coconut -- -D warnings
\ No newline at end of file
+ args: --features=coconut -- -D warnings
\ No newline at end of file
diff --git a/.github/workflows/build_matrix_includes.json b/.github/workflows/build_matrix_includes.json
new file mode 100644
index 0000000000..7f383efcbf
--- /dev/null
+++ b/.github/workflows/build_matrix_includes.json
@@ -0,0 +1,50 @@
+[
+ {
+ "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"
+ }
+]
\ No newline at end of file
diff --git a/.github/workflows/clippy_check.yml b/.github/workflows/clippy_check.yml
deleted file mode 100644
index 4a35898557..0000000000
--- a/.github/workflows/clippy_check.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-name: Clippy check
-
-on:
- push:
- paths-ignore:
- - 'explorer/**'
-
-jobs:
- clippy_check:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v1
- - run: rustup component add clippy
- - uses: actions-rs/clippy-check@v1
- with:
- token: ${{ secrets.GITHUB_TOKEN }}
- args: --all-features
\ No newline at end of file
diff --git a/.github/workflows/contract_matrix_includes.json b/.github/workflows/contract_matrix_includes.json
new file mode 100644
index 0000000000..134938eb53
--- /dev/null
+++ b/.github/workflows/contract_matrix_includes.json
@@ -0,0 +1,14 @@
+[
+ {
+ "rust":"stable",
+ "runOnEvent":"always"
+ },
+ {
+ "rust":"beta",
+ "runOnEvent":"pull_request"
+ },
+ {
+ "rust":"nightly",
+ "runOnEvent":"pull_request"
+ }
+]
\ No newline at end of file
diff --git a/.github/workflows/erc20_bridge_contract.yml b/.github/workflows/erc20_bridge_contract.yml
new file mode 100644
index 0000000000..9046fda870
--- /dev/null
+++ b/.github/workflows/erc20_bridge_contract.yml
@@ -0,0 +1,58 @@
+name: ERC20 Bridge Contract
+
+on: [ push, pull_request ]
+
+jobs:
+ matrix_prep:
+ runs-on: ubuntu-latest
+ outputs:
+ matrix: ${{ steps.set-matrix.outputs.matrix }}
+ steps:
+ # creates the matrix strategy from build_matrix_includes.json
+ - uses: actions/checkout@v2
+ - id: set-matrix
+ uses: JoshuaTheMiller/conditional-build-matrix@main
+ with:
+ inputFile: '.github/workflows/contract_matrix_includes.json'
+ filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
+ erc20-bridge-contract:
+ needs: matrix_prep
+ strategy:
+ matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
+ # 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' }}
+ 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/erc20-bridge/Cargo.toml --target wasm32-unknown-unknown
+
+ - uses: actions-rs/cargo@v1
+ with:
+ command: test
+ args: --manifest-path contracts/erc20-bridge/Cargo.toml
+
+ - uses: actions-rs/cargo@v1
+ with:
+ command: fmt
+ args: --manifest-path contracts/erc20-bridge/Cargo.toml -- --check
+
+ - uses: actions-rs/cargo@v1
+ if: ${{ matrix.rust != 'nightly' }}
+ with:
+ command: clippy
+ args: --manifest-path contracts/erc20-bridge/Cargo.toml -- -D warnings
diff --git a/.github/workflows/mixnet_contract.yml b/.github/workflows/mixnet_contract.yml
index 611decb81b..c2c57057c1 100644
--- a/.github/workflows/mixnet_contract.yml
+++ b/.github/workflows/mixnet_contract.yml
@@ -9,14 +9,26 @@ on:
- '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`]'
mixnet-contract:
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.rust == 'nightly' }}
+ needs: matrix_prep
strategy:
- matrix:
- rust: [ stable, beta, nightly ]
+ matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
steps:
- uses: actions/checkout@v2
@@ -29,6 +41,8 @@ jobs:
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
+ env:
+ RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/mixnet/Cargo.toml --target wasm32-unknown-unknown
diff --git a/.github/workflows/network-explorer-lint.yml b/.github/workflows/network-explorer-lint.yml
index 290403c0bb..f980a1ea03 100644
--- a/.github/workflows/network-explorer-lint.yml
+++ b/.github/workflows/network-explorer-lint.yml
@@ -11,7 +11,7 @@ defaults:
jobs:
build:
- runs-on: ubuntu-latest
+ runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
diff --git a/.github/workflows/network-explorer.yml b/.github/workflows/network-explorer.yml
index 3c7a1959c1..1dd2c89637 100644
--- a/.github/workflows/network-explorer.yml
+++ b/.github/workflows/network-explorer.yml
@@ -11,7 +11,7 @@ defaults:
jobs:
build:
- runs-on: ubuntu-latest
+ runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
diff --git a/.github/workflows/tauri-wallet-types.yml b/.github/workflows/tauri-wallet-types.yml
index 9569390145..0196d3459d 100644
--- a/.github/workflows/tauri-wallet-types.yml
+++ b/.github/workflows/tauri-wallet-types.yml
@@ -11,7 +11,7 @@ on:
jobs:
nym-wallet-types:
runs-on: ubuntu-latest
- if: github.event_name != 'pull_request'
+ 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
diff --git a/.github/workflows/wasm_client_build.yml b/.github/workflows/wasm_client_build.yml
index b9d40cb65b..cd064c4748 100644
--- a/.github/workflows/wasm_client_build.yml
+++ b/.github/workflows/wasm_client_build.yml
@@ -1,9 +1,6 @@
name: Wasm Client
on:
- push:
- paths-ignore:
- - 'explorer/**'
pull_request:
paths-ignore:
- 'explorer/**'
@@ -22,10 +19,11 @@ jobs:
override: true
components: rustfmt, clippy
- - uses: actions-rs/cargo@v1
- with:
- command: build
- args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
+# 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:
@@ -47,4 +45,4 @@ jobs:
# - uses: actions-rs/cargo@v1
# with:
# command: clippy
-# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
\ No newline at end of file
+# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
diff --git a/.gitignore b/.gitignore
index ac7542786c..32512e37ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,8 @@ 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
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 034a979e0f..c27860c548 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -93,6 +93,12 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
[[package]]
name = "arrayvec"
version = "0.7.1"
@@ -228,6 +234,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+[[package]]
+name = "az"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
+
[[package]]
name = "base-x"
version = "0.2.8"
@@ -296,6 +308,18 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+[[package]]
+name = "bitvec"
+version = "0.20.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
+
[[package]]
name = "blake2"
version = "0.8.1"
@@ -315,7 +339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcd555c66291d5f836dbb6883b48660ece810fe25a31f3bdfb911945dff2691f"
dependencies = [
"arrayref",
- "arrayvec",
+ "arrayvec 0.7.1",
"cc",
"cfg-if 1.0.0",
"constant_time_eq",
@@ -405,12 +429,24 @@ version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
+[[package]]
+name = "byte-slice-cast"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca0796d76a983651b4a0ddda16203032759f2fd9103d9181f7c65c06ee8872e6"
+
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+[[package]]
+name = "bytemuck"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
+
[[package]]
name = "byteorder"
version = "1.4.3"
@@ -943,6 +979,8 @@ name = "credentials"
version = "0.1.0"
dependencies = [
"coconut-interface",
+ "crypto",
+ "network-defaults",
"thiserror",
"url",
"validator-client",
@@ -1538,6 +1576,14 @@ dependencies = [
"termcolor",
]
+[[package]]
+name = "erc20-bridge-contract"
+version = "0.1.0"
+dependencies = [
+ "schemars",
+ "serde",
+]
+
[[package]]
name = "error-chain"
version = "0.12.4"
@@ -1547,6 +1593,49 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "ethabi"
+version = "14.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a01317735d563b3bad2d5f90d2e1799f414165408251abb762510f40e790e69a"
+dependencies = [
+ "anyhow",
+ "ethereum-types",
+ "hex",
+ "serde",
+ "serde_json",
+ "sha3",
+ "thiserror",
+ "uint",
+]
+
+[[package]]
+name = "ethbloom"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfb684ac8fa8f6c5759f788862bb22ec6fe3cb392f6bfd08e3c64b603661e3f8"
+dependencies = [
+ "crunchy",
+ "fixed-hash",
+ "impl-rlp",
+ "impl-serde",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "ethereum-types"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f64b5df66a228d85e4b17e5d6c6aa43b0310898ffe8a85988c4c032357aaabfd"
+dependencies = [
+ "ethbloom",
+ "fixed-hash",
+ "impl-rlp",
+ "impl-serde",
+ "primitive-types",
+ "uint",
+]
+
[[package]]
name = "explorer-api"
version = "0.1.0"
@@ -1642,6 +1731,30 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "fixed"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf"
+dependencies = [
+ "az",
+ "bytemuck",
+ "half",
+ "typenum",
+]
+
+[[package]]
+name = "fixed-hash"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
+dependencies = [
+ "byteorder",
+ "rand 0.8.4",
+ "rustc-hex",
+ "static_assertions",
+]
+
[[package]]
name = "flate2"
version = "1.0.22"
@@ -1738,6 +1851,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+[[package]]
+name = "funty"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
+
[[package]]
name = "futf"
version = "0.1.4"
@@ -1847,6 +1966,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
+[[package]]
+name = "futures-timer"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
+
[[package]]
name = "futures-util"
version = "0.3.17"
@@ -1882,20 +2007,27 @@ name = "gateway-client"
version = "0.1.0"
dependencies = [
"coconut-interface",
+ "credentials",
"crypto",
"fluvio-wasm-timer",
"futures",
"gateway-requests",
"getrandom 0.2.3",
+ "json",
"log",
+ "network-defaults",
"nymsphinx",
"rand 0.7.3",
+ "secp256k1",
+ "thiserror",
"tokio",
"tokio-tungstenite",
"tungstenite",
+ "url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-utils",
+ "web3",
]
[[package]]
@@ -2271,6 +2403,12 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "half"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+
[[package]]
name = "handlebars"
version = "3.5.5"
@@ -2352,6 +2490,12 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+[[package]]
+name = "hex-literal"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b"
+
[[package]]
name = "hkd32"
version = "0.6.0"
@@ -2583,6 +2727,44 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "impl-codec"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443"
+dependencies = [
+ "parity-scale-codec",
+]
+
+[[package]]
+name = "impl-rlp"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808"
+dependencies = [
+ "rlp",
+]
+
+[[package]]
+name = "impl-serde"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b47ca4d2b6931707a55fce5cf66aff80e2178c8b63bbb4ecb5695cbc870ddf6f"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "impl-trait-for-tuples"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5dacb10c5b3bb92d46ba347505a9041e676bb20ad220101326bffb0c93031ee"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "indenter"
version = "0.3.3"
@@ -2741,6 +2923,27 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "json"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
+
+[[package]]
+name = "jsonrpc-core"
+version = "18.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb"
+dependencies = [
+ "futures",
+ "futures-executor",
+ "futures-util",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
[[package]]
name = "k256"
version = "0.9.6"
@@ -2986,10 +3189,15 @@ dependencies = [
name = "mixnet-contract"
version = "0.1.0"
dependencies = [
+ "az",
"cosmwasm-std",
+ "fixed",
+ "log",
+ "network-defaults",
"schemars",
"serde",
"serde_repr",
+ "thiserror",
"ts-rs",
]
@@ -3107,6 +3315,7 @@ checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
name = "network-defaults"
version = "0.1.0"
dependencies = [
+ "hex-literal",
"serde",
"time 0.3.3",
"url",
@@ -3299,6 +3508,7 @@ dependencies = [
name = "nym-gateway"
version = "0.11.0"
dependencies = [
+ "bip39",
"clap",
"coconut-interface",
"config",
@@ -3307,12 +3517,15 @@ dependencies = [
"dashmap",
"dirs",
"dotenv",
+ "erc20-bridge-contract",
"futures",
+ "gateway-client",
"gateway-requests",
"humantime-serde",
"log",
"mixnet-client",
"mixnode-common",
+ "network-defaults",
"nymsphinx",
"pemstore",
"pretty_env_logger",
@@ -3327,6 +3540,7 @@ dependencies = [
"url",
"validator-client",
"version-checker",
+ "web3",
]
[[package]]
@@ -3438,6 +3652,8 @@ dependencies = [
"pin-project",
"pretty_env_logger",
"rand 0.7.3",
+ "rand 0.8.4",
+ "rand_chacha 0.3.1",
"reqwest",
"rocket",
"rocket_cors",
@@ -3713,6 +3929,32 @@ dependencies = [
"system-deps 3.2.0",
]
+[[package]]
+name = "parity-scale-codec"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909"
+dependencies = [
+ "arrayvec 0.7.1",
+ "bitvec",
+ "byte-slice-cast",
+ "impl-trait-for-tuples",
+ "parity-scale-codec-derive",
+ "serde",
+]
+
+[[package]]
+name = "parity-scale-codec-derive"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27"
+dependencies = [
+ "proc-macro-crate 1.1.0",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "parking"
version = "2.0.0"
@@ -4079,6 +4321,19 @@ dependencies = [
"log",
]
+[[package]]
+name = "primitive-types"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06345ee39fbccfb06ab45f3a1a5798d9dafa04cb8921a76d227040003a234b0e"
+dependencies = [
+ "fixed-hash",
+ "impl-codec",
+ "impl-rlp",
+ "impl-serde",
+ "uint",
+]
+
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
@@ -4249,6 +4504,12 @@ dependencies = [
"scheduled-thread-pool",
]
+[[package]]
+name = "radium"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
+
[[package]]
name = "rand"
version = "0.6.5"
@@ -4648,6 +4909,16 @@ dependencies = [
"opaque-debug 0.3.0",
]
+[[package]]
+name = "rlp"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5"
+dependencies = [
+ "bytes",
+ "rustc-hex",
+]
+
[[package]]
name = "rocket"
version = "0.5.0-rc.1"
@@ -4796,6 +5067,12 @@ dependencies = [
"quote",
]
+[[package]]
+name = "rustc-hex"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
+
[[package]]
name = "rustc_version"
version = "0.2.3"
@@ -4926,6 +5203,24 @@ dependencies = [
"untrusted",
]
+[[package]]
+name = "secp256k1"
+version = "0.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a"
+dependencies = [
+ "secp256k1-sys",
+]
+
+[[package]]
+name = "secp256k1-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "827cb7cce42533829c792fc51b82fbf18b125b45a702ef2c8be77fce65463a7b"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "security-framework"
version = "2.4.2"
@@ -5292,6 +5587,21 @@ dependencies = [
"ordered-buffer",
]
+[[package]]
+name = "soketto"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4919971d141dbadaa0e82b5d369e2d7666c98e4625046140615ca363e50d4daa"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures",
+ "httparse",
+ "log",
+ "rand 0.8.4",
+ "sha-1 0.9.8",
+]
+
[[package]]
name = "soup-sys"
version = "0.10.0"
@@ -5891,6 +6201,12 @@ dependencies = [
"x11-dl",
]
+[[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
[[package]]
name = "tar"
version = "0.4.37"
@@ -6283,6 +6599,15 @@ dependencies = [
"syn",
]
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
[[package]]
name = "tokio"
version = "1.12.0"
@@ -6380,6 +6705,7 @@ checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd"
dependencies = [
"bytes",
"futures-core",
+ "futures-io",
"futures-sink",
"log",
"pin-project-lite",
@@ -6851,6 +7177,52 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "web3"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd24abe6f2b68e0677f843059faea87bcbd4892e39f02886f366d8222c3c540d"
+dependencies = [
+ "arrayvec 0.5.2",
+ "base64",
+ "bytes",
+ "derive_more",
+ "ethabi",
+ "ethereum-types",
+ "futures",
+ "futures-timer",
+ "headers",
+ "hex",
+ "jsonrpc-core",
+ "log",
+ "parking_lot",
+ "pin-project",
+ "reqwest",
+ "rlp",
+ "secp256k1",
+ "serde",
+ "serde_json",
+ "soketto",
+ "tiny-keccak",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "url",
+ "web3-async-native-tls",
+]
+
+[[package]]
+name = "web3-async-native-tls"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb"
+dependencies = [
+ "native-tls",
+ "thiserror",
+ "tokio",
+ "url",
+]
+
[[package]]
name = "webkit2gtk"
version = "0.14.0"
@@ -7061,6 +7433,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "wyz"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
+
[[package]]
name = "x11-dl"
version = "2.19.1"
diff --git a/Cargo.toml b/Cargo.toml
index 02ceec5e95..b1e1a1c7f9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,6 +25,7 @@ members = [
"common/config",
"common/credentials",
"common/crypto",
+ "common/erc20-bridge-contract",
"common/mixnet-contract",
"common/mixnode-common",
"common/network-defaults",
@@ -62,4 +63,4 @@ default-members = [
"validator-api",
]
-exclude = ["explorer", "contracts"]
+exclude = ["explorer", "contracts", "tokenomics-py"]
diff --git a/README.md b/README.md
index 155ebc01ea..cd149437b8 100644
--- a/README.md
+++ b/README.md
@@ -5,8 +5,6 @@ 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.
@@ -33,6 +31,45 @@ 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|
+|---|---|
+|
|global share of rewards available, starts at 2% of the reward pool.
+|
|node reward for mixnode `i`.
+|
|ratio of total node stake (node bond + all delegations) to the token circulating supply.
+|
|ratio of stake operator has plaged to their node to the token circulating supply.
+|
|fraction of total effort undertaken by node `i`, set to `1/k` in testnet Milhon.
+|
|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `active set` size, and set to 5000 in testnet Milhon.
+|
|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
+|
|declared profit margin of operator `i`, defaults to 10% in testnet Milhon.
+|
|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
+|
|cost of operating node `i` for the duration of the rewarding eopoch, set to 40 Nym for testnet Milhon.
+
+Node reward for node `i` is determined as:
+
+
+
+where:
+
+
+
+and
+
+
+
+Operator of node `i` is credited with the following amount:
+
+
+
+Delegate with stake `s` recieves:
+
+
+
+where `s'` is stake `s` scaled over total token circulating supply.
+
### Licensing and copyright information
This program is available as open source under the terms of the Apache 2.0 license. However, some elements are being licensed under CC0-1.0 and MIT. For accurate information, please check individual files.
diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml
index 3d13ae8234..73078fbfdb 100644
--- a/clients/client-core/Cargo.toml
+++ b/clients/client-core/Cargo.toml
@@ -30,3 +30,6 @@ validator-client = { path = "../../common/client-libs/validator-client" }
[dev-dependencies]
tempfile = "3.1.0"
+
+[features]
+coconut = []
\ No newline at end of file
diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs
index 6f28610494..7a91bc345c 100644
--- a/clients/client-core/src/config/mod.rs
+++ b/clients/client-core/src/config/mod.rs
@@ -22,7 +22,10 @@ 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);
-const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
+// 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);
pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
@@ -100,6 +103,17 @@ impl Config {
self::Client::::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::::default_backup_bandwidth_token_keys_dir(&id);
+ }
+
self.client.id = id;
}
@@ -111,6 +125,16 @@ impl Config {
self.client.gateway_listener = gateway_listener.into();
}
+ #[cfg(not(feature = "coconut"))]
+ pub fn with_eth_private_key>(&mut self, eth_private_key: S) {
+ self.client.eth_private_key = eth_private_key.into();
+ }
+
+ #[cfg(not(feature = "coconut"))]
+ pub fn with_eth_endpoint>(&mut self, eth_endpoint: S) {
+ self.client.eth_endpoint = eth_endpoint.into();
+ }
+
pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec) {
self.client.validator_api_urls = validator_api_urls;
}
@@ -173,6 +197,21 @@ impl Config {
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
@@ -268,6 +307,20 @@ pub struct Client {
/// 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,
@@ -292,6 +345,12 @@ impl Default for Client {
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(),
}
@@ -326,6 +385,11 @@ impl Client {
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)]
diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs
index 66b33d7e3c..22ddb22f91 100644
--- a/clients/native/src/client/config/template.rs
+++ b/clients/native/src/client/config/template.rs
@@ -42,6 +42,17 @@ 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.
diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs
index 0cdde68aa3..49b8de71ff 100644
--- a/clients/native/src/client/mod.rs
+++ b/clients/native/src/client/mod.rs
@@ -35,10 +35,7 @@ use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage;
use tokio::runtime::Runtime;
-#[cfg(feature = "coconut")]
-use coconut_interface::Credential;
-#[cfg(feature = "coconut")]
-use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
+use gateway_client::bandwidth::BandwidthController;
pub(crate) mod config;
@@ -168,31 +165,6 @@ impl NymClient {
.start(self.runtime.handle())
}
- #[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,
@@ -212,7 +184,17 @@ impl NymClient {
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
- let coconut_credential = self.prepare_coconut_credential().await;
+ 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");
let mut gateway_client = GatewayClient::new(
gateway_address,
@@ -222,13 +204,11 @@ impl NymClient {
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
+ Some(bandwidth_controller),
);
gateway_client
- .authenticate_and_start(
- #[cfg(feature = "coconut")]
- Some(coconut_credential),
- )
+ .authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs
index 1994fe216e..a1e4aaeea9 100644
--- a/clients/native/src/commands/init.rs
+++ b/clients/native/src/commands/init.rs
@@ -22,7 +22,7 @@ use topology::{filter::VersionFilterable, gateway};
use url::Url;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
- App::new("init")
+ let app = App::new("init")
.about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id")
.long("id")
@@ -54,7 +54,21 @@ 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(not(feature = "coconut"))]
+ let app = app
+ .arg(Arg::with_name("eth_endpoint")
+ .long("eth_endpoint")
+ .help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
+ .takes_value(true)
+ .required(true))
+ .arg(Arg::with_name("eth_private_key")
+ .long("eth_private_key")
+ .help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
+ .takes_value(true)
+ .required(true));
+
+ app
}
async fn register_with_gateway(
diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs
index 475f14a9b6..b7ebad71fb 100644
--- a/clients/native/src/commands/mod.rs
+++ b/clients/native/src/commands/mod.rs
@@ -43,5 +43,14 @@ 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") {
+ config.get_base_mut().with_eth_endpoint(eth_endpoint);
+ }
+ #[cfg(not(feature = "coconut"))]
+ if let Some(eth_private_key) = matches.value_of("eth_private_key") {
+ config.get_base_mut().with_eth_private_key(eth_private_key);
+ }
+
config
}
diff --git a/clients/native/src/commands/run.rs b/clients/native/src/commands/run.rs
index d08f4ea764..9b46ec7bbc 100644
--- a/clients/native/src/commands/run.rs
+++ b/clients/native/src/commands/run.rs
@@ -10,7 +10,7 @@ use log::*;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
- App::new("run")
+ let app = App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id")
.long("id")
@@ -38,7 +38,19 @@ 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(not(feature = "coconut"))]
+ let app = app
+ .arg(Arg::with_name("eth_endpoint")
+ .long("eth_endpoint")
+ .help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
+ .takes_value(true))
+ .arg(Arg::with_name("eth_private_key")
+ .long("eth_private_key")
+ .help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
+ .takes_value(true));
+
+ app
}
// this only checks compatibility between config the binary. It does not take into consideration
diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs
index 70e196d3c1..775c97ddf4 100644
--- a/clients/socks5/src/client/config/template.rs
+++ b/clients/socks5/src/client/config/template.rs
@@ -42,6 +42,17 @@ 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.
diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs
index 3882694f20..c6e187145a 100644
--- a/clients/socks5/src/client/mod.rs
+++ b/clients/socks5/src/client/mod.rs
@@ -34,10 +34,7 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use tokio::runtime::Runtime;
-#[cfg(feature = "coconut")]
-use coconut_interface::Credential;
-#[cfg(feature = "coconut")]
-use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
+use gateway_client::bandwidth::BandwidthController;
pub(crate) mod config;
@@ -156,31 +153,6 @@ impl NymClient {
.start(self.runtime.handle())
}
- #[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,
@@ -200,7 +172,17 @@ impl NymClient {
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
- let coconut_credential = self.prepare_coconut_credential().await;
+ 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");
let mut gateway_client = GatewayClient::new(
gateway_address,
@@ -210,13 +192,11 @@ impl NymClient {
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
+ Some(bandwidth_controller),
);
gateway_client
- .authenticate_and_start(
- #[cfg(feature = "coconut")]
- Some(coconut_credential),
- )
+ .authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs
index f24dc53322..14bcbda8c6 100644
--- a/clients/socks5/src/commands/init.rs
+++ b/clients/socks5/src/commands/init.rs
@@ -20,7 +20,7 @@ use topology::{filter::VersionFilterable, gateway};
use url::Url;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
- App::new("init")
+ let app = App::new("init")
.about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id")
.long("id")
@@ -54,7 +54,21 @@ 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(not(feature = "coconut"))]
+ let app = app
+ .arg(Arg::with_name("eth_endpoint")
+ .long("eth_endpoint")
+ .help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
+ .takes_value(true)
+ .required(true))
+ .arg(Arg::with_name("eth_private_key")
+ .long("eth_private_key")
+ .help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
+ .takes_value(true)
+ .required(true));
+
+ app
}
async fn register_with_gateway(
diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs
index 7b6ff6c4b5..405b445c50 100644
--- a/clients/socks5/src/commands/mod.rs
+++ b/clients/socks5/src/commands/mod.rs
@@ -39,5 +39,14 @@ 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") {
+ config.get_base_mut().with_eth_endpoint(eth_endpoint);
+ }
+ #[cfg(not(feature = "coconut"))]
+ if let Some(eth_private_key) = matches.value_of("eth_private_key") {
+ config.get_base_mut().with_eth_private_key(eth_private_key);
+ }
+
config
}
diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs
index facd85144f..2ddf728d3a 100644
--- a/clients/socks5/src/commands/run.rs
+++ b/clients/socks5/src/commands/run.rs
@@ -10,7 +10,7 @@ use log::*;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
- App::new("run")
+ let app = App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id")
.long("id")
@@ -44,7 +44,19 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("port")
.help("Port for the socket to listen on")
.takes_value(true)
- )
+ );
+ #[cfg(not(feature = "coconut"))]
+ let app = app
+ .arg(Arg::with_name("eth_endpoint")
+ .long("eth_endpoint")
+ .help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
+ .takes_value(true))
+ .arg(Arg::with_name("eth_private_key")
+ .long("eth_private_key")
+ .help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
+ .takes_value(true));
+
+ app
}
// this only checks compatibility between config the binary. It does not take into consideration
diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs
index ed1397dc43..2ac0e8ef1c 100644
--- a/clients/webassembly/src/client/mod.rs
+++ b/clients/webassembly/src/client/mod.rs
@@ -17,10 +17,7 @@ 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};
+use gateway_client::bandwidth::BandwidthController;
pub(crate) mod received_processor;
@@ -103,35 +100,15 @@ 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 {
#[cfg(feature = "coconut")]
- 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 bandwidth_controller = Some(BandwidthController::new(
+ vec![self.validator_server.clone()],
+ *self.identity.public_key(),
+ ));
+ #[cfg(not(feature = "coconut"))]
+ let bandwidth_controller = None;
let mut client = self.get_and_update_topology().await;
let gateway = client.choose_gateway();
@@ -147,13 +124,11 @@ impl NymClient {
mixnet_messages_sender,
ack_sender,
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
+ bandwidth_controller,
);
gateway_client
- .authenticate_and_start(
- #[cfg(feature = "coconut")]
- Some(coconut_credential),
- )
+ .authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml
index 247352ac31..844191d8b6 100644
--- a/common/client-libs/gateway-client/Cargo.toml
+++ b/common/client-libs/gateway-client/Cargo.toml
@@ -10,14 +10,19 @@ 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"
@@ -31,6 +36,12 @@ 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"
diff --git a/common/client-libs/gateway-client/src/bandwidth.rs b/common/client-libs/gateway-client/src/bandwidth.rs
new file mode 100644
index 0000000000..12a68e6429
--- /dev/null
+++ b/common/client-libs/gateway-client/src/bandwidth.rs
@@ -0,0 +1,226 @@
+// Copyright 2021 - Nym Technologies SA
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::error::GatewayClientError;
+#[cfg(feature = "coconut")]
+use credentials::coconut::{
+ bandwidth::{obtain_signature, prepare_for_spending},
+ 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;
+#[cfg(not(feature = "coconut"))]
+use network_defaults::{
+ eth_contract::ETH_JSON_ABI, BANDWIDTH_VALUE, 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,
+};
+
+#[cfg(not(feature = "coconut"))]
+pub fn eth_contract(web3: Web3) -> Contract {
+ 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,
+ #[cfg(feature = "coconut")]
+ identity: PublicKey,
+ #[cfg(not(feature = "coconut"))]
+ contract: Contract,
+ #[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, 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 {
+ // 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 {
+ let verification_key = obtain_aggregate_verification_key(&self.validator_endpoints).await?;
+
+ let bandwidth_credential =
+ obtain_signature(&self.identity.to_bytes(), &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,
+ &verification_key,
+ )?)
+ }
+
+ #[cfg(not(feature = "coconut"))]
+ pub async fn prepare_token_credential(
+ &self,
+ gateway_identity: PublicKey,
+ ) -> Result {
+ 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 = 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 super::*;
+ use network_defaults::ETH_EVENT_NAME;
+
+ #[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());
+ }
+}
diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs
index da9fee7bd2..1ec592fb0f 100644
--- a/common/client-libs/gateway-client/src/client.rs
+++ b/common/client-libs/gateway-client/src/client.rs
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA
// SPDX-License-Identifier: Apache-2.0
+use crate::bandwidth::BandwidthController;
use crate::cleanup_socket_message;
use crate::error::GatewayClientError;
use crate::packet_router::PacketRouter;
@@ -8,6 +9,10 @@ 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;
@@ -30,15 +35,11 @@ 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,
- #[cfg(feature = "coconut")]
bandwidth_remaining: i64,
gateway_address: String,
gateway_identity: identity::PublicKey,
@@ -47,6 +48,7 @@ pub struct GatewayClient {
connection: SocketState,
packet_router: PacketRouter,
response_timeout_duration: Duration,
+ bandwidth_controller: Option,
// reconnection related variables
/// Specifies whether client should try to reconnect to gateway on connection failure.
@@ -69,20 +71,19 @@ impl GatewayClient {
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
response_timeout_duration: Duration,
+ bandwidth_controller: Option,
) -> Self {
GatewayClient {
authenticated: 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,
@@ -118,10 +119,7 @@ impl GatewayClient {
GatewayClient {
authenticated: false,
-
- #[cfg(feature = "coconut")]
bandwidth_remaining: 0,
-
gateway_address,
gateway_identity,
local_identity,
@@ -129,6 +127,7 @@ 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,
@@ -139,7 +138,6 @@ impl GatewayClient {
self.gateway_identity
}
- #[cfg(feature = "coconut")]
pub fn remaining_bandwidth(&self) -> i64 {
self.bandwidth_remaining
}
@@ -208,14 +206,7 @@ impl GatewayClient {
for i in 1..self.reconnection_attempts {
info!("attempt {}...", i);
- if self
- .authenticate_and_start(
- #[cfg(feature = "coconut")]
- None,
- )
- .await
- .is_ok()
- {
+ if self.authenticate_and_start().await.is_ok() {
info!("managed to reconnect!");
return Ok(());
}
@@ -234,13 +225,7 @@ impl GatewayClient {
// final attempt (done separately to be able to return a proper error)
info!("attempt {}", self.reconnection_attempts);
- match self
- .authenticate_and_start(
- #[cfg(feature = "coconut")]
- None,
- )
- .await
- {
+ match self.authenticate_and_start().await {
Ok(_) => {
info!("managed to reconnect!");
Ok(())
@@ -451,8 +436,12 @@ impl GatewayClient {
ClientControlRequest::new_authenticate(self_address, encrypted_address, iv).into();
match self.send_websocket_message(msg).await? {
- ServerResponse::Authenticate { status } => {
+ ServerResponse::Authenticate {
+ status,
+ bandwidth_remaining,
+ } => {
self.authenticated = status;
+ self.bandwidth_remaining = bandwidth_remaining;
Ok(())
}
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
@@ -478,22 +467,15 @@ impl GatewayClient {
}
#[cfg(feature = "coconut")]
- pub async fn claim_coconut_bandwidth(
+ async fn claim_coconut_bandwidth(
&mut self,
- coconut_credential: Credential,
+ 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(
- &coconut_credential,
+ &credential,
self.shared_key.as_ref().unwrap(),
iv,
)
@@ -507,7 +489,64 @@ impl GatewayClient {
Ok(())
}
- #[cfg(feature = "coconut")]
+ #[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(())
+ }
+
+ 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");
+
+ #[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;
+ }
+
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
packets
.iter()
@@ -522,9 +561,16 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
- #[cfg(feature = "coconut")]
- if self.estimate_required_bandwidth(&packets) < self.bandwidth_remaining {
- return Err(GatewayClientError::NotEnoughBandwidth);
+ 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,
+ ));
+ }
}
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -590,9 +636,16 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
- #[cfg(feature = "coconut")]
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
- return Err(GatewayClientError::NotEnoughBandwidth);
+ // 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,
+ ));
+ }
}
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -629,10 +682,6 @@ 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(());
}
@@ -660,22 +709,12 @@ impl GatewayClient {
Ok(())
}
- pub async fn authenticate_and_start(
- &mut self,
- #[cfg(feature = "coconut")] coconut_credential: Option,
- ) -> Result, GatewayClientError> {
+ pub async fn authenticate_and_start(&mut self) -> Result, 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()?;
diff --git a/common/client-libs/gateway-client/src/error.rs b/common/client-libs/gateway-client/src/error.rs
index 170314e484..f396b3e06b 100644
--- a/common/client-libs/gateway-client/src/error.rs
+++ b/common/client-libs/gateway-client/src/error.rs
@@ -2,39 +2,83 @@
// 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)]
+#[derive(Debug, Error)]
pub enum GatewayClientError {
+ #[error("Connection to the gateway is not established")]
ConnectionNotEstablished,
+
+ #[error("Gateway returned an error response - {0}")]
GatewayError(String),
- NetworkError(WsError),
+
+ #[error("There was a network error - {0}")]
+ NetworkError(#[from] WsError),
// TODO: see if `JsValue` is a reasonable type for this
#[cfg(target_arch = "wasm32")]
+ #[error("There was a network error")]
NetworkErrorWasm(JsValue),
- NoSharedKeyAvailable,
- ConnectionAbruptlyClosed,
- MalformedResponse,
- SerializeCredential,
- NotAuthenticated,
- NotEnoughBandwidth,
- UnexpectedResponse,
- ConnectionInInvalidState,
- RegistrationFailure(HandshakeError),
- AuthenticationFailure,
- Timeout,
-}
+ #[cfg(not(feature = "coconut"))]
+ #[error("Could not backup keypair - {0}")]
+ IOError(#[from] std::io::Error),
-impl From for GatewayClientError {
- fn from(err: WsError) -> Self {
- GatewayClientError::NetworkError(err)
- }
+ #[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")]
+ 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 GatewayClientError {
@@ -54,60 +98,3 @@ impl GatewayClientError {
}
}
}
-
-#[cfg(target_arch = "wasm32")]
-impl From 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")
- }
- }
- }
-}
diff --git a/common/client-libs/gateway-client/src/lib.rs b/common/client-libs/gateway-client/src/lib.rs
index f1f0987392..dc7b17d8b4 100644
--- a/common/client-libs/gateway-client/src/lib.rs
+++ b/common/client-libs/gateway-client/src/lib.rs
@@ -8,6 +8,7 @@ pub use packet_router::{
};
use tungstenite::{protocol::Message, Error as WsError};
+pub mod bandwidth;
pub mod client;
pub mod error;
pub mod packet_router;
diff --git a/common/client-libs/gateway-client/src/socket_state.rs b/common/client-libs/gateway-client/src/socket_state.rs
index b82e9907b3..9fba57208f 100644
--- a/common/client-libs/gateway-client/src/socket_state.rs
+++ b/common/client-libs/gateway-client/src/socket_state.rs
@@ -119,7 +119,7 @@ impl PartiallyDelegated {
}
.is_err()
{
- panic!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
+ warn!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
}
};
diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs
index 0e2533b37d..c50a9cffd0 100644
--- a/common/client-libs/validator-client/src/client.rs
+++ b/common/client-libs/validator-client/src/client.rs
@@ -10,9 +10,9 @@ use mixnet_contract::StateParams;
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
-#[cfg(feature = "nymd-client")]
-use mixnet_contract::RawDelegationData;
use mixnet_contract::{GatewayBond, MixNodeBond};
+#[cfg(feature = "nymd-client")]
+use mixnet_contract::{RawDelegationData, RewardingIntervalResponse};
use url::Url;
#[cfg(feature = "nymd-client")]
@@ -172,6 +172,43 @@ impl Client {
Ok(self.nymd.get_state_params().await?)
}
+ pub async fn get_current_rewarding_interval(
+ &self,
+ ) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.nymd.get_current_rewarding_interval().await?)
+ }
+
+ pub async fn get_reward_pool(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.nymd.get_reward_pool().await?.u128())
+ }
+
+ pub async fn get_circulating_supply(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.nymd.get_circulating_supply().await?.u128())
+ }
+
+ pub async fn get_sybil_resistance_percent(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.nymd.get_sybil_resistance_percent().await?)
+ }
+
+ pub async fn get_epoch_reward_percent(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.nymd.get_epoch_reward_percent().await?)
+ }
+
// basically handles paging for us
pub async fn get_all_nymd_mixnodes(&self) -> Result, ValidatorClientError>
where
diff --git a/common/client-libs/validator-client/src/nymd/fee_helpers.rs b/common/client-libs/validator-client/src/nymd/fee_helpers.rs
index f63daf83b4..95b505704f 100644
--- a/common/client-libs/validator-client/src/nymd/fee_helpers.rs
+++ b/common/client-libs/validator-client/src/nymd/fee_helpers.rs
@@ -23,10 +23,11 @@ pub enum Operation {
BondGateway,
UnbondGateway,
- DelegateToGateway,
- UndelegateFromGateway,
UpdateStateParams,
+
+ BeginMixnodeRewarding,
+ FinishMixnodeRewarding,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
@@ -43,13 +44,13 @@ impl fmt::Display for Operation {
Operation::Send => f.write_str("Send"),
Operation::BondMixnode => f.write_str("BondMixnode"),
Operation::UnbondMixnode => f.write_str("UnbondMixnode"),
- Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
- Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::BondGateway => f.write_str("BondGateway"),
Operation::UnbondGateway => f.write_str("UnbondGateway"),
- Operation::DelegateToGateway => f.write_str("DelegateToGateway"),
- Operation::UndelegateFromGateway => f.write_str("UndelegateFromGateway"),
+ Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
+ Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::UpdateStateParams => f.write_str("UpdateStateParams"),
+ Operation::BeginMixnodeRewarding => f.write_str("BeginMixnodeRewarding"),
+ Operation::FinishMixnodeRewarding => f.write_str("FinishMixnodeRewarding"),
}
}
}
@@ -71,10 +72,10 @@ impl Operation {
Operation::BondGateway => 175_000u64.into(),
Operation::UnbondGateway => 175_000u64.into(),
- Operation::DelegateToGateway => 175_000u64.into(),
- Operation::UndelegateFromGateway => 175_000u64.into(),
Operation::UpdateStateParams => 175_000u64.into(),
+ Operation::BeginMixnodeRewarding => 175_000u64.into(),
+ Operation::FinishMixnodeRewarding => 175_000u64.into(),
}
}
diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs
index 54477ca013..815076a664 100644
--- a/common/client-libs/validator-client/src/nymd/mod.rs
+++ b/common/client-libs/validator-client/src/nymd/mod.rs
@@ -11,12 +11,13 @@ use crate::nymd::fee_helpers::Operation;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
-use cosmwasm_std::Coin;
+use cosmwasm_std::{Coin, Uint128};
use mixnet_contract::{
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
LayerDistribution, MixNode, MixOwnershipResponse, PagedAllDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
- PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, StateParams,
+ PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, RewardingIntervalResponse,
+ StateParams,
};
use serde::Serialize;
use std::collections::HashMap;
@@ -27,10 +28,11 @@ pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
pub use crate::nymd::gas_price::GasPrice;
pub use cosmrs::rpc::HttpClient as QueryNymdClient;
pub use cosmrs::tendermint::block::Height;
+pub use cosmrs::tendermint::hash;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::{Fee, Gas};
pub use cosmrs::Coin as CosmosCoin;
-pub use cosmrs::{AccountId, Denom};
+pub use cosmrs::{AccountId, Decimal, Denom};
pub use signing_client::Client as SigningNymdClient;
pub mod cosmwasm_client;
@@ -184,6 +186,21 @@ impl NymdClient {
self.client.get_height().await
}
+ /// Obtains the hash of a block specified by the provided height.
+ ///
+ /// # Arguments
+ ///
+ /// * `height`: height of the block for which we want to obtain the hash.
+ pub async fn get_block_hash(&self, height: u32) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ self.client
+ .get_block(Some(height))
+ .await
+ .map(|block| block.block_id.hash)
+ }
+
pub async fn get_balance(&self, address: &AccountId) -> Result