Compare commits
260 Commits
mix-tcp-stuff
...
create
| Author | SHA1 | Date | |
|---|---|---|---|
| 589ee64516 | |||
| b20ab5dc50 | |||
| 5bdff28a11 | |||
| 4795a643a4 | |||
| f7f6421415 | |||
| 891cfb80ea | |||
| 9344296804 | |||
| 3538b5237e | |||
| 5581f735d2 | |||
| c0ede6a506 | |||
| 5d6b84a94f | |||
| 66fff0edf0 | |||
| 2bdb623101 | |||
| 1f435880d7 | |||
| 34579222c5 | |||
| 2a43134327 | |||
| 844bcba6e8 | |||
| f3ac17eb9d | |||
| 6296d09adf | |||
| 2ae81f6da0 | |||
| 1d5e8b62ac | |||
| 581cdd5bdf | |||
| e2e49e7136 | |||
| dff82f946f | |||
| ec61728654 | |||
| 61471e9058 | |||
| ed4fd84503 | |||
| cb4b0403b5 | |||
| da8e513627 | |||
| 3f6de8b10c | |||
| 1e01a8e633 | |||
| aaf3dca5b9 | |||
| f939cae3d9 | |||
| 1db61f800c | |||
| 5096c1e60e | |||
| 7e36595d8f | |||
| 515aedac60 | |||
| dca2b2c763 | |||
| c92f09543e | |||
| b45eb16783 | |||
| f8523dc7d1 | |||
| 996ce6a233 | |||
| 1dcb0a0456 | |||
| 55b99e4ce1 | |||
| bd94dd3055 | |||
| 0d19bb4ddc | |||
| dcd70155cd | |||
| a469aeff05 | |||
| 107199bd9c | |||
| 53524447c4 | |||
| bc832c97d8 | |||
| 4989d47ea2 | |||
| 3cb69780a6 | |||
| 68b61bfa84 | |||
| 06fca9bd1f | |||
| 4c10cebf1b | |||
| aea962b546 | |||
| ab2a1c3fe5 | |||
| 8d0c040015 | |||
| 42efff83da | |||
| 86fe955592 | |||
| c3ce0d0b5c | |||
| 0e2cfa5be0 | |||
| 17a5872c6d | |||
| 969155bf91 | |||
| f4fafbfea5 | |||
| 5e97b1f79a | |||
| b52bf951a6 | |||
| 98805a11e4 | |||
| 0a2f28b0ec | |||
| db1ad4dcab | |||
| 78ca539018 | |||
| 04cafc72dc | |||
| 63812994a1 | |||
| 19dee11539 | |||
| 6f3a6b7855 | |||
| 7c84ad4384 | |||
| bc647fc8e2 | |||
| 429ff6045d | |||
| 0de1deced4 | |||
| fc2eedfc66 | |||
| 7ddd819ff3 | |||
| 83b416d12d | |||
| b9c775c3ae | |||
| b613cf87c8 | |||
| 6f669866e9 | |||
| 4e61fefec8 | |||
| b4514ecd83 | |||
| 4f6902525e | |||
| 881139e36f | |||
| 32e2557456 | |||
| 8b44820e51 | |||
| 5e6417f837 | |||
| 3d5ac0b883 | |||
| dfb2a2f380 | |||
| d1de751850 | |||
| ecee6ca863 | |||
| 31ea3f92e2 | |||
| f19c934fae | |||
| 10d6f20de7 | |||
| 96b33bfbe4 | |||
| 444c787d0a | |||
| 61fcd4ac69 | |||
| b76802e6eb | |||
| 7d351029a4 | |||
| 4ee445c119 | |||
| 61ddeea495 | |||
| 7b802033b3 | |||
| b484f47369 | |||
| 66979df10c | |||
| 82f161fb91 | |||
| 9d0fd681d4 | |||
| c2ab47a102 | |||
| 8704c21621 | |||
| 03ffb25bf9 | |||
| 70db1ad062 | |||
| 952ed9b642 | |||
| f57fe79686 | |||
| 9179f1c351 | |||
| c4f7a1e09d | |||
| 701012a968 | |||
| 9767f72b8f | |||
| de0fb7459d | |||
| e2ead6dbe1 | |||
| 7b10d92ca4 | |||
| 2c6e5eb673 | |||
| 02fde4e530 | |||
| cc25fc1f32 | |||
| c971e486b5 | |||
| 96a9eb6f6a | |||
| 9eeb61ea0a | |||
| 08042c61ad | |||
| 36c74f30e5 | |||
| fd1d437211 | |||
| 4956d13bdc | |||
| 6478736654 | |||
| d9f6c0723e | |||
| f86050d916 | |||
| 52f5656190 | |||
| 21cd90f238 | |||
| 4e51188d35 | |||
| 22eb199936 | |||
| a2fc1bbc96 | |||
| 621599692f | |||
| 3ad3837c87 | |||
| 4d745e3b7e | |||
| 3a053b8dd6 | |||
| 1f144690da | |||
| eec1895acc | |||
| 72e243042e | |||
| 99864cb7a9 | |||
| 3155728119 | |||
| c253b22f69 | |||
| 66f3a3e9a8 | |||
| 65a1d6d91e | |||
| 44cf9b054b | |||
| 39e2473ef3 | |||
| 93a108863c | |||
| 0905593123 | |||
| ed9223d5a3 | |||
| c2ad4e5bb4 | |||
| 5f7f5ef92d | |||
| 962684ff56 | |||
| 7b3804c078 | |||
| 170f1823e1 | |||
| dc2020559a | |||
| 2b9444cce3 | |||
| 68c1c068ac | |||
| 3d0b70a237 | |||
| 65a6edc78c | |||
| 2ec8349897 | |||
| 38a2d94f80 | |||
| c7fa910516 | |||
| 2fe08274dd | |||
| be89d848dc | |||
| a230a9b8b9 | |||
| 72eae7cdf3 | |||
| 7cae195370 | |||
| dfb16e385c | |||
| 660e1cad0a | |||
| 7c1aa57a7e | |||
| a06e496f78 | |||
| 70599b97b9 | |||
| 02b194bde0 | |||
| 20ec049db5 | |||
| ebac4e8564 | |||
| da81664729 | |||
| fec3d46b33 | |||
| a4eb3a7dbf | |||
| 28d15f2c4f | |||
| 6078787daa | |||
| 6d93f36c82 | |||
| c6f93e38f5 | |||
| 2159f71888 | |||
| a9abea3446 | |||
| 8e2713c9ba | |||
| 2ba0ef0e35 | |||
| d3713cbc79 | |||
| 4d3fb2b585 | |||
| e65e611859 | |||
| ebfb9c4bc1 | |||
| 8e7918cc45 | |||
| c465eb3efc | |||
| b90136ac4e | |||
| ae5373168d | |||
| 6f3942f6b7 | |||
| 4a25725a11 | |||
| 13f38343aa | |||
| d34c829174 | |||
| f75b4843e8 | |||
| 6963ead65a | |||
| 0425cdeaf0 | |||
| 789914b096 | |||
| 008afe7a85 | |||
| 8acd3a0975 | |||
| fa1519ec6a | |||
| da94b7e8df | |||
| 4aa8cb4647 | |||
| 5d13538508 | |||
| 4bbbcf629d | |||
| 34ef970fd8 | |||
| fec570613c | |||
| 14a904eff0 | |||
| 24ffb8fe8c | |||
| d1bf1f2005 | |||
| c42b3f7617 | |||
| 606144dd87 | |||
| a5bcbcc1f5 | |||
| 31cd62b676 | |||
| b43844bd7a | |||
| 53366c4151 | |||
| 2ca63a6fd5 | |||
| d33baec79b | |||
| 0626e54b6a | |||
| 2a260d46f8 | |||
| 7cb5afdeab | |||
| d18f4c0380 | |||
| c9c4059c90 | |||
| cd89feb57e | |||
| 17553d606e | |||
| 3fe33dec8b | |||
| 589074360d | |||
| 2e5406692c | |||
| b6d9ed960b | |||
| 1920177b2f | |||
| 1d89a887fb | |||
| f562433519 | |||
| 2affa76304 | |||
| d6aeb9142f | |||
| 3092c8e68e | |||
| 4a1efab5c8 | |||
| 06ebe56914 | |||
| 1c3140898f | |||
| 9ebe8310a1 | |||
| cf5c5f1df4 | |||
| f30cfc0be7 | |||
| 9460a9941c | |||
| eeffc0db51 | |||
| b7d6e0e1e3 | |||
| 45e0b406ac |
@@ -41,8 +41,8 @@ jobs:
|
||||
# This is a workaround replacement which builds on the last working commit b332a6b55668f60988e36961f3f62a794ba82ddb and then on current branch
|
||||
- name: Save current branch to ~/current_branch
|
||||
run: git rev-parse --abbrev-ref HEAD > ~/current_branch
|
||||
- name: Git pull & switch to b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
run: git pull && git checkout b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
- name: Git pull, reset & switch to b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
run: git pull && git reset --hard && git checkout b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
- name: Build all projects in documentation/ & move to ~/dist/docs/ from b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
run: cd documentation && ./build_all_to_dist.sh
|
||||
|
||||
|
||||
@@ -8,11 +8,6 @@ on:
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
enable_wireguard:
|
||||
description: "Add --features wireguard"
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
enable_deb:
|
||||
description: "True to enable cargo-deb installation and .deb package building"
|
||||
required: false
|
||||
@@ -42,7 +37,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-20.04]
|
||||
platform: [ ubuntu-20.04 ]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
@@ -70,9 +65,6 @@ jobs:
|
||||
- name: Set CARGO_FEATURES
|
||||
run: |
|
||||
echo 'CARGO_FEATURES=--features wireguard' >> $GITHUB_ENV
|
||||
if: >
|
||||
github.event_name == 'schedule' ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.enable_wireguard == true)
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -104,12 +96,9 @@ jobs:
|
||||
name: nym-binaries-artifacts
|
||||
path: |
|
||||
target/release/nym-client
|
||||
target/release/nym-gateway
|
||||
target/release/nym-mixnode
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-api
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
@@ -124,12 +113,9 @@ jobs:
|
||||
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
|
||||
run: |
|
||||
cp target/release/nym-client $OUTPUT_DIR
|
||||
cp target/release/nym-gateway $OUTPUT_DIR
|
||||
cp target/release/nym-mixnode $OUTPUT_DIR
|
||||
cp target/release/nym-socks5-client $OUTPUT_DIR
|
||||
cp target/release/nym-api $OUTPUT_DIR
|
||||
cp target/release/nym-network-requester $OUTPUT_DIR
|
||||
cp target/release/nym-network-statistics $OUTPUT_DIR
|
||||
cp target/release/nymvisor $OUTPUT_DIR
|
||||
cp target/release/nym-node $OUTPUT_DIR
|
||||
cp target/release/nym-cli $OUTPUT_DIR
|
||||
|
||||
@@ -6,7 +6,6 @@ on:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'explorer-api/**'
|
||||
- 'ephemera/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
@@ -24,7 +23,6 @@ on:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'explorer-api/**'
|
||||
- 'ephemera/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-20.04]
|
||||
platform: [ ubuntu-20.04 ]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
@@ -58,6 +58,7 @@ jobs:
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_dkg.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_ecash.wasm $OUTPUT_DIR
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
|
||||
@@ -46,8 +46,8 @@ jobs:
|
||||
# This is a workaround replacement which builds on the last working commit b332a6b55668f60988e36961f3f62a794ba82ddb and then on current branch
|
||||
- name: Save current branch to ~/current_branch
|
||||
run: git rev-parse --abbrev-ref HEAD > ~/current_branch
|
||||
- name: Git pull & switch to b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
run: git pull && git checkout b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
- name: Git pull, reset & switch to b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
run: git pull && git reset --hard && git checkout b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
- name: Build all projects in documentation/ & move to ~/dist/docs/ from b332a6b55668f60988e36961f3f62a794ba82ddb
|
||||
run: cd documentation && ./build_all_to_dist.sh
|
||||
|
||||
|
||||
@@ -27,23 +27,17 @@ jobs:
|
||||
release_id: ${{ steps.create-release.outputs.id }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }}
|
||||
client_hash: ${{ steps.binary-hashes.outputs.client_hash }}
|
||||
mixnode_hash: ${{ steps.binary-hashes.outputs.mixnode_hash }}
|
||||
gateway_hash: ${{ steps.binary-hashes.outputs.gateway_hash }}
|
||||
nymvisor_hash: ${{ steps.binary-hashes.outputs.nymvisor_hash }}
|
||||
nymnode_hash: ${{ steps.binary-hashes.outputs.nymnode_hash }}
|
||||
socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }}
|
||||
netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }}
|
||||
cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }}
|
||||
netstat_hash: ${{ steps.binary-hashes.outputs.netstat_hash }}
|
||||
client_version: ${{ steps.binary-versions.outputs.client_version }}
|
||||
mixnode_version: ${{ steps.binary-versions.outputs.mixnode_version }}
|
||||
gateway_version: ${{ steps.binary-versions.outputs.gateway_version }}
|
||||
nymvisor_version: ${{ steps.binary-versions.outputs.nymvisor_version }}
|
||||
nymnode_version: ${{ steps.binary-versions.outputs.nymnode_version }}
|
||||
socks5_version: ${{ steps.binary-versions.outputs.socks5_version }}
|
||||
netreq_version: ${{ steps.binary-versions.outputs.netreq_version }}
|
||||
cli_version: ${{ steps.binary-versions.outputs.cli_version }}
|
||||
netstat_version: ${{ steps.binary-versions.outputs.netstat_version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -57,6 +51,10 @@ jobs:
|
||||
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
|
||||
|
||||
- name: Set CARGO_FEATURES
|
||||
run: |
|
||||
echo 'CARGO_FEATURES=--features wireguard' >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -66,8 +64,8 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
|
||||
args: --workspace --release ${{ env.CARGO_FEATURES }}
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -75,12 +73,9 @@ jobs:
|
||||
path: |
|
||||
target/release/explorer-api
|
||||
target/release/nym-client
|
||||
target/release/nym-gateway
|
||||
target/release/nym-mixnode
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-api
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
@@ -94,12 +89,9 @@ jobs:
|
||||
files: |
|
||||
target/release/explorer-api
|
||||
target/release/nym-client
|
||||
target/release/nym-gateway
|
||||
target/release/nym-mixnode
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-api
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
|
||||
+110
-1
@@ -4,6 +4,116 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2024.9-topdeck] (2024-07-26)
|
||||
|
||||
- chore: fix 1.80 lint issues ([#4731])
|
||||
- Handle clients with different versions in IPR ([#4723])
|
||||
- Add 1GB/day/user bandwidth cap ([#4717])
|
||||
- Feature/merge back ([#4710])
|
||||
- removed mixnode/gateway config migration code and disabled cli without explicit flag ([#4706])
|
||||
|
||||
[#4731]: https://github.com/nymtech/nym/pull/4731
|
||||
[#4723]: https://github.com/nymtech/nym/pull/4723
|
||||
[#4717]: https://github.com/nymtech/nym/pull/4717
|
||||
[#4710]: https://github.com/nymtech/nym/pull/4710
|
||||
[#4706]: https://github.com/nymtech/nym/pull/4706
|
||||
|
||||
## [2024.8-wispa] (2024-07-10)
|
||||
|
||||
- add event parsing to support cosmos_sdk > 0.50 ([#4697])
|
||||
- Fix NR config compatibility ([#4690])
|
||||
- Remove UserAgent constructor since it's weakly typed ([#4689])
|
||||
- [bugfix]: Node_api_check CLI looked over roles on blacklisted nodes ([#4687])
|
||||
- Add mixnodes to self describing api cache ([#4684])
|
||||
- Move and whole bump of crates to workspace and upgrade some ([#4680])
|
||||
- Remove code that refers to removed nym-network-statistics ([#4679])
|
||||
- Remove nym-network-statistics ([#4678])
|
||||
- Create UserAgent that can be passed from the binary to the nym api client ([#4677])
|
||||
- Add authenticator ([#4667])
|
||||
|
||||
[#4697]: https://github.com/nymtech/nym/pull/4697
|
||||
[#4690]: https://github.com/nymtech/nym/pull/4690
|
||||
[#4689]: https://github.com/nymtech/nym/pull/4689
|
||||
[#4687]: https://github.com/nymtech/nym/pull/4687
|
||||
[#4684]: https://github.com/nymtech/nym/pull/4684
|
||||
[#4680]: https://github.com/nymtech/nym/pull/4680
|
||||
[#4679]: https://github.com/nymtech/nym/pull/4679
|
||||
[#4678]: https://github.com/nymtech/nym/pull/4678
|
||||
[#4677]: https://github.com/nymtech/nym/pull/4677
|
||||
[#4667]: https://github.com/nymtech/nym/pull/4667
|
||||
|
||||
## [2024.7-doubledecker] (2024-07-04)
|
||||
|
||||
- Add an early return in `parse_raw_str_logs` for empty raw log strings. ([#4686])
|
||||
- Bump braces from 3.0.2 to 3.0.3 in /wasm/mix-fetch/internal-dev ([#4672])
|
||||
- add expiry returned on import ([#4670])
|
||||
- [bugfix] missing rustls feature ([#4666])
|
||||
- Bump ws from 8.13.0 to 8.17.1 in /wasm/client/internal-dev-node ([#4665])
|
||||
- Bump braces from 3.0.2 to 3.0.3 in /clients/native/examples/js-examples/websocket ([#4663])
|
||||
- Bump ws from 8.14.2 to 8.17.1 in /sdk/typescript/packages/nodejs-client ([#4662])
|
||||
- Update setup.md ([#4661])
|
||||
- New clippy lints ([#4660])
|
||||
- Bump braces from 3.0.2 to 3.0.3 in /nym-api/tests ([#4659])
|
||||
- Bump braces from 3.0.2 to 3.0.3 in /docker/typescript_client/upload_contract ([#4658])
|
||||
- Update vps-setup.md ([#4656])
|
||||
- Update configuration.md ([#4655])
|
||||
- Remove old PR template ([#4639])
|
||||
|
||||
[#4686]: https://github.com/nymtech/nym/pull/4686
|
||||
[#4672]: https://github.com/nymtech/nym/pull/4672
|
||||
[#4670]: https://github.com/nymtech/nym/pull/4670
|
||||
[#4666]: https://github.com/nymtech/nym/pull/4666
|
||||
[#4665]: https://github.com/nymtech/nym/pull/4665
|
||||
[#4663]: https://github.com/nymtech/nym/pull/4663
|
||||
[#4662]: https://github.com/nymtech/nym/pull/4662
|
||||
[#4661]: https://github.com/nymtech/nym/pull/4661
|
||||
[#4660]: https://github.com/nymtech/nym/pull/4660
|
||||
[#4659]: https://github.com/nymtech/nym/pull/4659
|
||||
[#4658]: https://github.com/nymtech/nym/pull/4658
|
||||
[#4656]: https://github.com/nymtech/nym/pull/4656
|
||||
[#4655]: https://github.com/nymtech/nym/pull/4655
|
||||
[#4639]: https://github.com/nymtech/nym/pull/4639
|
||||
|
||||
## [2024.6-chomp] (2024-06-25)
|
||||
|
||||
- Remove additional code as part of Ephemera Purge and SP and contracts ([#4650])
|
||||
- bugfix: make sure nym-api can handle non-cw2 (or without detailed build info) compliant contracts ([#4648])
|
||||
- introduced a flag to accept toc and exposed it via self-described API ([#4647])
|
||||
- bugfix: make sure to return an error on invalid public ip ([#4646])
|
||||
- Add ci check for PR having an assigned milestone ([#4644])
|
||||
- Removed ephemera code ([#4642])
|
||||
- Remove stale peers ([#4640])
|
||||
- Add generic wg private network routing ([#4636])
|
||||
- Feature/new node endpoints ([#4635])
|
||||
- standarised ContractBuildInformation and added it to all contracts ([#4631])
|
||||
- validate nym-node public ips on startup ([#4630])
|
||||
- Bump defguard wg ([#4625])
|
||||
- Fix cargo warnings ([#4624])
|
||||
- Update kernel peers on peer modification ([#4622])
|
||||
- Handle v6 and v7 requests in the IPR, but reply with v6 ([#4620])
|
||||
- fix typo ([#4619])
|
||||
- Update crypto and rand crates ([#4607])
|
||||
- Purge name service and service provider directory contracts ([#4603])
|
||||
|
||||
[#4650]: https://github.com/nymtech/nym/pull/4650
|
||||
[#4648]: https://github.com/nymtech/nym/pull/4648
|
||||
[#4647]: https://github.com/nymtech/nym/pull/4647
|
||||
[#4646]: https://github.com/nymtech/nym/pull/4646
|
||||
[#4644]: https://github.com/nymtech/nym/pull/4644
|
||||
[#4642]: https://github.com/nymtech/nym/pull/4642
|
||||
[#4640]: https://github.com/nymtech/nym/pull/4640
|
||||
[#4636]: https://github.com/nymtech/nym/pull/4636
|
||||
[#4635]: https://github.com/nymtech/nym/pull/4635
|
||||
[#4631]: https://github.com/nymtech/nym/pull/4631
|
||||
[#4630]: https://github.com/nymtech/nym/pull/4630
|
||||
[#4625]: https://github.com/nymtech/nym/pull/4625
|
||||
[#4624]: https://github.com/nymtech/nym/pull/4624
|
||||
[#4622]: https://github.com/nymtech/nym/pull/4622
|
||||
[#4620]: https://github.com/nymtech/nym/pull/4620
|
||||
[#4619]: https://github.com/nymtech/nym/pull/4619
|
||||
[#4607]: https://github.com/nymtech/nym/pull/4607
|
||||
[#4603]: https://github.com/nymtech/nym/pull/4603
|
||||
|
||||
## [2024.5-ragusa] (2024-05-22)
|
||||
|
||||
- Feature/nym node api location ([#4605])
|
||||
@@ -441,7 +551,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#3187]: https://github.com/nymtech/nym/issues/3187
|
||||
[#3203]: https://github.com/nymtech/nym/pull/3203
|
||||
[#3199]: https://github.com/nymtech/nym/pull/3199
|
||||
>>>>>>> master
|
||||
|
||||
## [v1.1.13] (2023-03-15)
|
||||
|
||||
|
||||
Generated
+1195
-465
File diff suppressed because it is too large
Load Diff
+47
-17
@@ -20,6 +20,7 @@ members = [
|
||||
"clients/native",
|
||||
"clients/native/websocket-requests",
|
||||
"clients/socks5",
|
||||
"common/authenticator-requests",
|
||||
"common/async-file-watcher",
|
||||
"common/bandwidth-controller",
|
||||
"common/bin-common",
|
||||
@@ -33,6 +34,7 @@ members = [
|
||||
"common/commands",
|
||||
"common/config",
|
||||
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
||||
"common/cosmwasm-smart-contracts/ecash-contract",
|
||||
"common/cosmwasm-smart-contracts/coconut-dkg",
|
||||
"common/cosmwasm-smart-contracts/contracts-common",
|
||||
"common/cosmwasm-smart-contracts/group-contract",
|
||||
@@ -46,8 +48,12 @@ members = [
|
||||
"common/credentials-interface",
|
||||
"common/crypto",
|
||||
"common/dkg",
|
||||
"common/ecash-double-spending",
|
||||
"common/ecash-time",
|
||||
"common/execute",
|
||||
"common/exit-policy",
|
||||
"common/gateway-requests",
|
||||
"common/gateway-storage",
|
||||
"common/http-api-client",
|
||||
"common/http-api-common",
|
||||
"common/inclusion-probability",
|
||||
@@ -58,6 +64,7 @@ members = [
|
||||
"common/node-tester-utils",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
"common/nymcoconut",
|
||||
"common/nym_offline_compact_ecash",
|
||||
"common/nym-id",
|
||||
"common/nym-metrics",
|
||||
"common/nymsphinx",
|
||||
@@ -73,10 +80,10 @@ members = [
|
||||
"common/nymsphinx/types",
|
||||
"common/nyxd-scraper",
|
||||
"common/pemstore",
|
||||
"common/serde-helpers",
|
||||
"common/socks5-client-core",
|
||||
"common/socks5/proxy-helpers",
|
||||
"common/socks5/requests",
|
||||
"common/statistics",
|
||||
"common/store-cipher",
|
||||
"common/task",
|
||||
"common/topology",
|
||||
@@ -91,15 +98,14 @@ members = [
|
||||
"explorer-api/explorer-api-requests",
|
||||
"explorer-api/explorer-client",
|
||||
"gateway",
|
||||
"gateway/gateway-requests",
|
||||
"integrations/bity",
|
||||
"mixnode",
|
||||
"sdk/lib/socks5-listener",
|
||||
"sdk/rust/nym-sdk",
|
||||
"service-providers/authenticator",
|
||||
"service-providers/common",
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
"service-providers/network-statistics",
|
||||
"nym-api",
|
||||
"nym-browser-extension/storage",
|
||||
"nym-api/nym-api-requests",
|
||||
@@ -120,6 +126,8 @@ members = [
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
"wasm/zknym-lib",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -127,19 +135,19 @@ default-members = [
|
||||
"clients/socks5",
|
||||
"gateway",
|
||||
"service-providers/network-requester",
|
||||
"service-providers/network-statistics",
|
||||
"mixnode",
|
||||
"nym-api",
|
||||
"tools/nymvisor",
|
||||
"explorer-api",
|
||||
"nym-validator-rewarder",
|
||||
"nym-node"
|
||||
"nym-node",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"explorer",
|
||||
"contracts",
|
||||
"nym-wallet",
|
||||
"nym-vpn/ui/src-tauri",
|
||||
"cpu-cycles",
|
||||
"sdk/ffi/cpp",
|
||||
]
|
||||
@@ -164,8 +172,13 @@ axum-extra = "0.9.3"
|
||||
base64 = "0.21.4"
|
||||
bincode = "1.3.3"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
|
||||
# can we unify those?
|
||||
bit-vec = "0.7.0"
|
||||
bitvec = "1.0.0"
|
||||
|
||||
blake3 = "1.3.1"
|
||||
bloomfilter = "1.0.14"
|
||||
bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
bytes = "1.5.0"
|
||||
@@ -192,12 +205,15 @@ cupid = "0.6.1"
|
||||
curve25519-dalek = "4.1"
|
||||
dashmap = "5.5.3"
|
||||
defguard_wireguard_rs = "0.4.2"
|
||||
digest = "0.10.7"
|
||||
dirs = "4.0"
|
||||
doc-comment = "0.3"
|
||||
dotenvy = "0.15.6"
|
||||
ecdsa = "0.16"
|
||||
ed25519-dalek = "2.1"
|
||||
etherparse = "0.13.0"
|
||||
eyre = "0.6.9"
|
||||
fastrand = "2.1.0"
|
||||
flate2 = "1.0.28"
|
||||
futures = "0.3.28"
|
||||
generic-array = "0.14.7"
|
||||
@@ -209,15 +225,16 @@ hex = "0.4.3"
|
||||
hex-literal = "0.3.3"
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12.1"
|
||||
http = "1"
|
||||
httpcodec = "0.2.3"
|
||||
humantime = "2.1.0"
|
||||
humantime-serde = "1.1.1"
|
||||
http = "1"
|
||||
hyper = "1.3.1"
|
||||
indexed_db_futures = "0.3.0"
|
||||
inquire = "0.6.2"
|
||||
ip_network = "0.4.1"
|
||||
ipnetwork = "0.16"
|
||||
isocountry = "0.3.2"
|
||||
itertools = "0.13.0"
|
||||
k256 = "0.13"
|
||||
lazy_static = "1.4.0"
|
||||
ledger-transport = "0.10.0"
|
||||
@@ -239,6 +256,7 @@ publicsuffix = "2.2.3"
|
||||
quote = "1"
|
||||
rand = "0.8.5"
|
||||
rand-07 = "0.7.3"
|
||||
rand_chacha = "0.3"
|
||||
rand_chacha_02 = "0.2"
|
||||
rand_core = "0.6.3"
|
||||
rand_distr = "0.4"
|
||||
@@ -252,6 +270,7 @@ rocket_cors = "0.6.0"
|
||||
rocket_okapi = "0.8.0"
|
||||
safer-ffi = "0.1.4"
|
||||
schemars = "0.8.1"
|
||||
semver = "1.0.23"
|
||||
serde = "1.0.152"
|
||||
serde_bytes = "0.11.6"
|
||||
serde_derive = "1.0"
|
||||
@@ -259,22 +278,25 @@ serde_json = "1.0.91"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.4.0"
|
||||
serde_yaml = "0.9.25"
|
||||
sha2 = "0.10.8"
|
||||
si-scale = "0.2.2"
|
||||
sphinx-packet = "0.1.1"
|
||||
sqlx = "0.6.3"
|
||||
strum = "0.25"
|
||||
subtle-encoding = "0.5"
|
||||
syn = "1"
|
||||
sysinfo = "0.30.12"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.40"
|
||||
tempfile = "3.5.0"
|
||||
thiserror = "1.0.48"
|
||||
time = "0.3.30"
|
||||
tokio = "1.33.0"
|
||||
tokio-stream = "0.1.14"
|
||||
tokio-test = "0.4.2"
|
||||
tokio = "1.39"
|
||||
tokio-stream = "0.1.15"
|
||||
tokio-test = "0.4.4"
|
||||
tokio-tungstenite = { version = "0.20.1" }
|
||||
tokio-util = "0.7.10"
|
||||
tokio-util = "0.7.11"
|
||||
toml = "0.8.14"
|
||||
tower = "0.4.13"
|
||||
tower-http = "0.5.2"
|
||||
tracing = "0.1.37"
|
||||
@@ -289,6 +311,7 @@ utoipa-swagger-ui = "6.0.0"
|
||||
vergen = { version = "=8.3.1", default-features = false }
|
||||
walkdir = "2"
|
||||
wasm-bindgen-test = "0.3.36"
|
||||
x25519-dalek = "2.0.0"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
prometheus = { version = "0.13.0" }
|
||||
@@ -296,7 +319,8 @@ prometheus = { version = "0.13.0" }
|
||||
# coconut/DKG related
|
||||
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
|
||||
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
|
||||
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "feature/gt-serialization-0.8.0" }
|
||||
# plus to make our live easier we need serde support from https://github.com/zkcrypto/bls12_381/pull/125
|
||||
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect" }
|
||||
group = { version = "0.13.0", default-features = false }
|
||||
ff = { version = "0.13.0", default-features = false }
|
||||
|
||||
@@ -319,16 +343,22 @@ cw-controllers = { version = "=1.1.0" }
|
||||
# cosmrs-related
|
||||
bip32 = { version = "0.5.1", default-features = false }
|
||||
|
||||
# temporarily using a fork again (yay.) because we need staking and slashing support
|
||||
cosmrs = { git = "https://github.com/jstuczyn/cosmos-rust", branch = "nym-temp/all-validator-features" }
|
||||
#cosmrs = { git = "https://github.com/jstuczyn/cosmos-rust", branch = "nym-temp/all-validator-features" } # unfortuntely we need a fork by yours truly to get the staking support
|
||||
tendermint = "0.34" # same version as used by cosmrs
|
||||
tendermint-rpc = "0.34" # same version as used by cosmrs
|
||||
# temporarily using a fork again (yay.) because we need staking and slashing support (which are already on main but not released)
|
||||
# plus response message parsing (which is, as of the time of writing this message, waiting to get merged)
|
||||
#cosmrs = { path = "../cosmos-rust-fork/cosmos-rust/cosmrs" }
|
||||
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev = "4b1332e6d8258ac845cef71589c8d362a669675a" } # unfortuntely we need a fork by yours truly to get the staking support
|
||||
tendermint = "0.37.0" # same version as used by cosmrs
|
||||
tendermint-rpc = "0.37.0" # same version as used by cosmrs
|
||||
prost = { version = "0.12", default-features = false }
|
||||
|
||||
# wasm-related dependencies
|
||||
gloo-utils = "0.2.0"
|
||||
gloo-net = "0.5.0"
|
||||
|
||||
# use a separate branch due to feature unification failures
|
||||
# this is blocked until the upstream removes outdates `wasm_bindgen` feature usage
|
||||
# indexed_db_futures = "0.4.1"
|
||||
indexed_db_futures = { git = "https://github.com/TiemenSch/rust-indexed-db", branch = "update-uuid" }
|
||||
js-sys = "0.3.69"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
tsify = "0.4.5"
|
||||
|
||||
@@ -133,7 +133,7 @@ clippy: sdk-wasm-lint
|
||||
# Build contracts ready for deploy
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
CONTRACTS=vesting_contract mixnet_contract
|
||||
CONTRACTS=vesting_contract mixnet_contract nym_ecash
|
||||
CONTRACTS_WASM=$(addsuffix .wasm, $(CONTRACTS))
|
||||
CONTRACTS_OUT_DIR=contracts/target/wasm32-unknown-unknown/release
|
||||
|
||||
|
||||
@@ -7,86 +7,66 @@ SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
The platform is composed of multiple Rust crates. Top-level executable binary crates include:
|
||||
|
||||
* nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
|
||||
* nym-client - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
|
||||
* nym-socks5-client - a Socks5 proxy you can run on your machine and use with existing applications.
|
||||
* nym-gateway - acts sort of like a mailbox for mixnet messages, which removes the need for direct 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-node` - a tool for running a node within the Nym network. Nym Nodes containing functionality such as `mixnode`, `entry-gateway` and `exit-gateway` are fundamental components of Nym Mixnet architecture. Nym Nodes are ran by decentralised node operators. Read more about `nym-node` in [Operators Guide documentation](https://nymtech.net/operators/nodes/nym-node.html). Network functionality of `nym-node` (labeled with `--mode` flag) can be:
|
||||
- `mixnode` - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
|
||||
- `gateway` - acts sort of like a mailbox for mixnet messages, which removes the need for direct delivery to potentially offline or firewalled devices. Gateways can be further categorized as `entry-gateway` and `exit-gateway`. The latter has an extra embedded IP packet router and Network requester to route data to the internet.
|
||||
* `nym-client` - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
|
||||
* `nym-socks5-client` - a Socks5 proxy you can run on your machine and use with existing applications.
|
||||
* `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.
|
||||
<!-- coming soon
|
||||
* `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").
|
||||
-->
|
||||
|
||||
```ascii
|
||||
┌─►mix──┐ mix mix
|
||||
│ │
|
||||
Entry │ │ Exit
|
||||
client ───► Gateway ──┘ mix │ mix ┌─►mix ───► Gateway ───► internet
|
||||
│ │
|
||||
│ │
|
||||
mix └─►mix──┘ mix
|
||||
|
||||
```
|
||||
|
||||
[](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
|
||||
|
||||
|
||||
### Building
|
||||
|
||||
Platform build instructions are available on [our docs site](https://nymtech.net/docs/binaries/pre-built-binaries.html).
|
||||
Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/wallet/desktop-wallet.html).
|
||||
* Platform build instructions are available on Nym [Operators Guide documentation](https://nymtech.net/operators/binaries/building-nym.html).
|
||||
* Wallet build instructions are available on Nym [Technical docs](https://nymtech.net/docs/wallet/desktop-wallet.html).
|
||||
|
||||
### Developing
|
||||
|
||||
There's a `.env.sample-dev` file provided which you can rename to `.env` if you want convenient logging, backtrace, or other environment variables pre-set. The `.env` file is ignored so you don't need to worry about checking it in.
|
||||
There's a [`sandbox.env`](https://github.com/nymtech/nym/envs/sandbox.env) file provided which you can rename to `.env` if you want convenient testing environment. Read more about sandbox environment in our [Operators Guide page](https://nymtech.net/operators/sandbox.html).
|
||||
|
||||
For Typescript components, please see [ts-packages](./ts-packages).
|
||||
References for developers:
|
||||
|
||||
* [Developers Portal](https://nymtech.net/developers)
|
||||
* [Typescript SDKs](https://sdk.nymtech.net/)
|
||||
* [Technical Documentation - Nym network overview](https://nymtech.net/docs/)
|
||||
* [Release Cycle - git flow](https://nymtech.net/operators/release-cycle.html)
|
||||
|
||||
### Developer chat
|
||||
|
||||
> We used to use Keybase for developer chats, but we have since migrated to Matrix and Discord. We no longer check the old **nymtech.friends** Keybase team.
|
||||
|
||||
You can chat to us in two places:
|
||||
* The #dev channel on [Matrix](https://matrix.to/#/#dev:nymtech.chat)
|
||||
* The various developer channels on [Discord](https://discord.gg/nym)
|
||||
* The various developer channels on [Discord](https://nymtech.net/go/discord)
|
||||
|
||||
### Rewards
|
||||
### Tokenomics & 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#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}R#gh-dark-mode-only">|global share of rewards available, starts at 2% of the reward pool.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=R_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}R_{i}#gh-dark-mode-only">|node reward for mixnode `i`.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=\sigma_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\sigma_{i}#gh-dark-mode-only">|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}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda_{i}#gh-dark-mode-only">|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}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\omega_{i}#gh-dark-mode-only">|fraction of total effort undertaken by node `i`, set to `1/k`.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=k#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}k#gh-dark-mode-only">|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#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|A Sybil attack resistance parameter - the higher this parameter is set, the stronger the reduction in competitiveness for a Sybil attacker.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10%.
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PF_{i}#gh-dark-mode-only">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|
||||
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMs.
|
||||
|
||||
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)#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}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)#gh-dark-mode-only">
|
||||
where:
|
||||
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\sigma^'_{i} = min\{\sigma_{i}, 1/k\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}\sigma^'_{i} = min\{\sigma_{i}, 1/k\}#gh-dark-mode-only">
|
||||
|
||||
and
|
||||
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\lambda^'_{i} = min\{\lambda_{i}, 1/k\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda^'_{i} = min\{\lambda_{i}, 1/k\}#gh-dark-mode-only">
|
||||
|
||||
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})\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
|
||||
|
||||
Delegate with stake `s` receives:
|
||||
|
||||
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
|
||||
<img src="https://render.githubusercontent.com/render/math?math=\color{white}max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
|
||||
|
||||
where `s'` is stake `s` scaled over total token circulating supply.
|
||||
Nym network economic incentives, operator and validator rewards, and scalability of the network are determined according to the principles laid out in the section 6 of [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf).
|
||||
Initial reward pool is set to 250 million Nym, making the circulating supply 750 million Nym.
|
||||
|
||||
### Licensing and copyright information
|
||||
|
||||
This is a monorepo and components that make up Nym as a system are licensed individually, so for accurate information, please check individual files.
|
||||
|
||||
As a general approach, licensing is as follows this pattern:
|
||||
|
||||
- applications and binaries are GPLv3
|
||||
- libraries and components are Apache 2.0 or MIT
|
||||
- documentation is Apache 2.0 or CC0-1.0
|
||||
|
||||
Again, for accurate information, please check individual files.
|
||||
Nym Node Operators and Validators Temrs and Conditions can be found [here](https://nymtech.net/terms-and-conditions/operators/v1.0.0).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.35"
|
||||
version = "1.1.39"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
@@ -23,33 +23,49 @@ url = { workspace = true }
|
||||
|
||||
bs58 = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "derive"] }
|
||||
dirs = "4.0"
|
||||
dirs = { workspace = true }
|
||||
log = { workspace = true } # self explanatory
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
|
||||
serde = { workspace = true, features = [
|
||||
"derive",
|
||||
] } # for config serialization/deserialization
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tap = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } # async runtime
|
||||
tokio = { workspace = true, features = [
|
||||
"rt-multi-thread",
|
||||
"net",
|
||||
"signal",
|
||||
] } # async runtime
|
||||
tokio-tungstenite = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
## internal
|
||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
|
||||
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "fs-gateways-storage", "cli"] }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = [
|
||||
"output_format",
|
||||
"clap",
|
||||
] }
|
||||
nym-client-core = { path = "../../common/client-core", features = [
|
||||
"fs-credentials-storage",
|
||||
"fs-surb-storage",
|
||||
"fs-gateways-storage",
|
||||
"cli",
|
||||
] }
|
||||
nym-config = { path = "../../common/config" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-crypto = { path = "../../common/crypto" }
|
||||
nym-gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
nym-gateway-requests = { path = "../../common/gateway-requests" }
|
||||
nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
nym-sphinx = { path = "../../common/nymsphinx" }
|
||||
nym-pemstore = { path = "../../common/pemstore" }
|
||||
nym-task = { path = "../../common/task" }
|
||||
nym-topology = { path = "../../common/topology" }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client", features = ["http-client"] }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"http-client",
|
||||
] }
|
||||
nym-client-websocket-requests = { path = "websocket-requests" }
|
||||
nym-id = { path = "../../common/nym-id" }
|
||||
|
||||
|
||||
@@ -106,8 +106,10 @@ impl SocketClient {
|
||||
};
|
||||
|
||||
let storage = self.initialise_storage().await?;
|
||||
let user_agent = nym_bin_common::bin_info!().into();
|
||||
|
||||
let mut base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client);
|
||||
let mut base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client)
|
||||
.with_user_agent(user_agent);
|
||||
|
||||
if let Some(custom_mixnet) = &self.custom_mixnet {
|
||||
base_client = base_client.with_stored_topology(custom_mixnet)?;
|
||||
|
||||
@@ -22,8 +22,9 @@ impl AsRef<CommonClientAddGatewayArgs> for Args {
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
|
||||
let user_agent = nym_bin_common::bin_info!().into();
|
||||
let output = args.output;
|
||||
let res = add_gateway::<CliNativeClient, _>(args).await?;
|
||||
let res = add_gateway::<CliNativeClient, _>(args, Some(user_agent)).await?;
|
||||
|
||||
println!("{}", output.format(&res));
|
||||
Ok(())
|
||||
|
||||
@@ -114,8 +114,9 @@ impl Display for InitResults {
|
||||
pub(crate) async fn execute(args: Init) -> Result<(), ClientError> {
|
||||
eprintln!("Initialising client...");
|
||||
|
||||
let user_agent = nym_bin_common::bin_info!().into();
|
||||
let output = args.output;
|
||||
let res = initialise_client::<CliNativeClient>(args).await?;
|
||||
let res = initialise_client::<CliNativeClient>(args, Some(user_agent)).await?;
|
||||
|
||||
let init_results = InitResults::new(res);
|
||||
println!("{}", output.format(&init_results));
|
||||
|
||||
@@ -26,6 +26,7 @@ pub(crate) mod import_credential;
|
||||
pub(crate) mod init;
|
||||
mod list_gateways;
|
||||
pub(crate) mod run;
|
||||
mod show_ticketbooks;
|
||||
mod switch_gateway;
|
||||
|
||||
pub(crate) struct CliNativeClient;
|
||||
@@ -84,6 +85,9 @@ pub(crate) enum Commands {
|
||||
/// Change the currently active gateway. Note that you must have already registered with the new gateway!
|
||||
SwitchGateway(switch_gateway::Args),
|
||||
|
||||
/// Display information associated with the imported ticketbooks,
|
||||
ShowTicketbooks(show_ticketbooks::Args),
|
||||
|
||||
/// Show build information of this binary
|
||||
BuildInfo(build_info::BuildInfo),
|
||||
|
||||
@@ -116,6 +120,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
|
||||
Commands::ListGateways(args) => list_gateways::execute(args).await?,
|
||||
Commands::AddGateway(args) => add_gateway::execute(args).await?,
|
||||
Commands::SwitchGateway(args) => switch_gateway::execute(args).await?,
|
||||
Commands::ShowTicketbooks(args) => show_ticketbooks::execute(args).await?,
|
||||
Commands::BuildInfo(m) => build_info::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::CliNativeClient;
|
||||
use crate::error::ClientError;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::cli_helpers::client_show_ticketbooks::{
|
||||
show_ticketbooks, CommonShowTicketbooksArgs,
|
||||
};
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
#[command(flatten)]
|
||||
common_args: CommonShowTicketbooksArgs,
|
||||
|
||||
#[arg(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
impl AsRef<CommonShowTicketbooksArgs> for Args {
|
||||
fn as_ref(&self) -> &CommonShowTicketbooksArgs {
|
||||
&self.common_args
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
|
||||
let output = args.output;
|
||||
let res = show_ticketbooks::<CliNativeClient, _>(args).await?;
|
||||
|
||||
println!("{}", output.format(&res));
|
||||
Ok(())
|
||||
}
|
||||
@@ -422,7 +422,7 @@ impl Handler {
|
||||
) {
|
||||
// We don't want a crash in the connection handler to trigger a shutdown of the whole
|
||||
// process.
|
||||
task_client.mark_as_success();
|
||||
task_client.disarm();
|
||||
|
||||
let ws_stream = match accept_async(socket).await {
|
||||
Ok(ws_stream) => ws_stream,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.35"
|
||||
version = "1.1.39"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
@@ -11,7 +11,9 @@ license.workspace = true
|
||||
bs58 = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "derive"] }
|
||||
log = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
|
||||
serde = { workspace = true, features = [
|
||||
"derive",
|
||||
] } # for config serialization/deserialization
|
||||
serde_json = { workspace = true }
|
||||
tap = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
@@ -22,20 +24,31 @@ url = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
# internal
|
||||
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
|
||||
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "fs-gateways-storage", "cli"] }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = [
|
||||
"output_format",
|
||||
"clap",
|
||||
] }
|
||||
nym-client-core = { path = "../../common/client-core", features = [
|
||||
"fs-credentials-storage",
|
||||
"fs-surb-storage",
|
||||
"fs-gateways-storage",
|
||||
"cli",
|
||||
] }
|
||||
nym-config = { path = "../../common/config" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-crypto = { path = "../../common/crypto" }
|
||||
nym-gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-gateway-requests = { path = "../../common/gateway-requests" }
|
||||
nym-id = { path = "../../common/nym-id" }
|
||||
nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
nym-sphinx = { path = "../../common/nymsphinx" }
|
||||
nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
|
||||
nym-pemstore = { path = "../../common/pemstore" }
|
||||
nym-topology = { path = "../../common/topology" }
|
||||
nym-socks5-client-core = { path = "../../common/socks5-client-core" }
|
||||
nym-id = { path = "../../common/nym-id" }
|
||||
nym-sphinx = { path = "../../common/nymsphinx" }
|
||||
nym-topology = { path = "../../common/topology" }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"http-client",
|
||||
] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -22,8 +22,9 @@ impl AsRef<CommonClientAddGatewayArgs> for Args {
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
|
||||
let user_agent = nym_bin_common::bin_info!().into();
|
||||
let output = args.output;
|
||||
let res = add_gateway::<CliSocks5Client, _>(args).await?;
|
||||
let res = add_gateway::<CliSocks5Client, _>(args, Some(user_agent)).await?;
|
||||
|
||||
println!("{}", output.format(&res));
|
||||
Ok(())
|
||||
|
||||
@@ -129,8 +129,9 @@ impl Display for InitResults {
|
||||
pub(crate) async fn execute(args: Init) -> Result<(), Socks5ClientError> {
|
||||
eprintln!("Initialising client...");
|
||||
|
||||
let user_agent = nym_bin_common::bin_info!().into();
|
||||
let output = args.output;
|
||||
let res = initialise_client::<CliSocks5Client>(args).await?;
|
||||
let res = initialise_client::<CliSocks5Client>(args, Some(user_agent)).await?;
|
||||
|
||||
let init_results = InitResults::new(res);
|
||||
println!("{}", output.format(&init_results));
|
||||
|
||||
@@ -30,6 +30,7 @@ mod import_credential;
|
||||
pub mod init;
|
||||
mod list_gateways;
|
||||
pub(crate) mod run;
|
||||
mod show_ticketbooks;
|
||||
mod switch_gateway;
|
||||
|
||||
pub(crate) struct CliSocks5Client;
|
||||
@@ -88,6 +89,9 @@ pub(crate) enum Commands {
|
||||
/// Change the currently active gateway. Note that you must have already registered with the new gateway!
|
||||
SwitchGateway(switch_gateway::Args),
|
||||
|
||||
/// Display information associated with the imported ticketbooks,
|
||||
ShowTicketbooks(show_ticketbooks::Args),
|
||||
|
||||
/// Show build information of this binary
|
||||
BuildInfo(build_info::BuildInfo),
|
||||
|
||||
@@ -123,6 +127,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
|
||||
Commands::ListGateways(args) => list_gateways::execute(args).await?,
|
||||
Commands::AddGateway(args) => add_gateway::execute(args).await?,
|
||||
Commands::SwitchGateway(args) => switch_gateway::execute(args).await?,
|
||||
Commands::ShowTicketbooks(args) => show_ticketbooks::execute(args).await?,
|
||||
Commands::BuildInfo(m) => build_info::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||
|
||||
@@ -116,7 +116,13 @@ pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn std::error::Error +
|
||||
let storage =
|
||||
OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug)
|
||||
.await?;
|
||||
NymClient::new(config.core, storage, args.common_args.custom_mixnet)
|
||||
.run_forever()
|
||||
.await
|
||||
let user_agent = nym_bin_common::bin_info!().into();
|
||||
NymClient::new(
|
||||
config.core,
|
||||
storage,
|
||||
user_agent,
|
||||
args.common_args.custom_mixnet,
|
||||
)
|
||||
.run_forever()
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::CliSocks5Client;
|
||||
use crate::error::Socks5ClientError;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::cli_helpers::client_show_ticketbooks::{
|
||||
show_ticketbooks, CommonShowTicketbooksArgs,
|
||||
};
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
#[command(flatten)]
|
||||
common_args: CommonShowTicketbooksArgs,
|
||||
|
||||
#[arg(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
impl AsRef<CommonShowTicketbooksArgs> for Args {
|
||||
fn as_ref(&self) -> &CommonShowTicketbooksArgs {
|
||||
&self.common_args
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
|
||||
let output = args.output;
|
||||
let res = show_ticketbooks::<CliSocks5Client, _>(args).await?;
|
||||
|
||||
println!("{}", output.format(&res));
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "nym-authenticator-requests"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bincode = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-wireguard-types = { path = "../wireguard-types" }
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod v1;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 1;
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
bincode::DefaultOptions::new()
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
const VERSION: u8 = 1;
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::{GatewayClient, InitMessage, PeerPublicKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuthenticatorRequest {
|
||||
pub version: u8,
|
||||
pub data: AuthenticatorRequestData,
|
||||
pub reply_to: Recipient,
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
impl AuthenticatorRequest {
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn new_initial_request(init_message: InitMessage, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorRequestData::Initial(init_message),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_final_request(gateway_client: GatewayClient, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorRequestData::Final(gateway_client),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(peer_public_key: PeerPublicKey, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum AuthenticatorRequestData {
|
||||
Initial(InitMessage),
|
||||
Final(GatewayClient),
|
||||
QueryBandwidth(PeerPublicKey),
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuthenticatorResponse {
|
||||
pub version: u8,
|
||||
pub data: AuthenticatorResponseData,
|
||||
pub reply_to: Recipient,
|
||||
}
|
||||
|
||||
impl AuthenticatorResponse {
|
||||
pub fn new_pending_registration_success(
|
||||
registration_data: RegistrationData,
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
|
||||
reply: registration_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegistredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorResponseData::Registered(RegisteredResponse {
|
||||
reply: registred_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_remaining_bandwidth(
|
||||
remaining_bandwidth_data: Option<RemainingBandwidthData>,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recipient(&self) -> Recipient {
|
||||
self.reply_to
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum AuthenticatorResponseData {
|
||||
PendingRegistration(PendingRegistrationResponse),
|
||||
Registered(RegisteredResponse),
|
||||
RemainingBandwidth(RemainingBandwidthResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PendingRegistrationResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistrationData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RemainingBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: Option<RemainingBandwidthData>,
|
||||
}
|
||||
@@ -14,13 +14,14 @@ thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
nym-coconut = { path = "../nymcoconut" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-credentials = { path = "../credentials" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-validator-client]
|
||||
path = "../client-libs/validator-client"
|
||||
|
||||
@@ -1,87 +1,126 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::BandwidthControllerError;
|
||||
use nym_credential_storage::models::StorableIssuedCredential;
|
||||
use crate::utils::{get_coin_index_signatures, get_expiration_date_signatures};
|
||||
use log::info;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::coconut::bandwidth::{CredentialType, IssuanceBandwidthCredential};
|
||||
use nym_credentials::coconut::utils::obtain_aggregate_signature;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_validator_client::coconut::all_coconut_api_clients;
|
||||
use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use nym_credentials::ecash::bandwidth::IssuanceTicketBook;
|
||||
use nym_credentials::ecash::utils::obtain_aggregate_wallet;
|
||||
use nym_credentials::IssuedTicketBook;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_ecash_time::{ecash_default_expiration_date, Date};
|
||||
use nym_validator_client::coconut::all_ecash_api_clients;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::EcashSigningClient;
|
||||
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, EcashQueryClient};
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ToSingletonContractData;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::rngs::OsRng;
|
||||
use state::State;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub mod state;
|
||||
|
||||
pub async fn deposit<C>(client: &C, amount: Coin) -> Result<State, BandwidthControllerError>
|
||||
pub async fn make_deposit<C>(
|
||||
client: &C,
|
||||
client_id: &[u8],
|
||||
expiration: Option<Date>,
|
||||
ticketbook_type: TicketType,
|
||||
) -> Result<IssuanceTicketBook, BandwidthControllerError>
|
||||
where
|
||||
C: CoconutBandwidthSigningClient + Sync,
|
||||
C: EcashSigningClient + EcashQueryClient + Sync,
|
||||
{
|
||||
let mut rng = OsRng;
|
||||
let signing_key = identity::PrivateKey::new(&mut rng);
|
||||
let encryption_key = encryption::PrivateKey::new(&mut rng);
|
||||
let expiration = expiration.unwrap_or_else(ecash_default_expiration_date);
|
||||
|
||||
let tx_hash = client
|
||||
.deposit(
|
||||
amount.clone(),
|
||||
CredentialType::Voucher.to_string(),
|
||||
let deposit_amount = client.get_required_deposit_amount().await?;
|
||||
info!("we'll need to deposit {deposit_amount} to obtain the ticketbook");
|
||||
let result = client
|
||||
.make_ticketbook_deposit(
|
||||
signing_key.public_key().to_base58_string(),
|
||||
encryption_key.public_key().to_base58_string(),
|
||||
deposit_amount.into(),
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
.transaction_hash;
|
||||
.await?;
|
||||
|
||||
let voucher =
|
||||
IssuanceBandwidthCredential::new_voucher(amount, tx_hash, signing_key, encryption_key);
|
||||
let deposit_id = result.parse_singleton_u32_contract_data()?;
|
||||
|
||||
let state = State { voucher };
|
||||
info!("our ticketbook deposit has been stored under id {deposit_id}");
|
||||
|
||||
Ok(state)
|
||||
Ok(IssuanceTicketBook::new_with_expiration(
|
||||
deposit_id,
|
||||
client_id,
|
||||
signing_key,
|
||||
ticketbook_type,
|
||||
expiration,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_bandwidth_voucher<C, St>(
|
||||
state: &State,
|
||||
pub async fn query_and_persist_required_global_signatures<S>(
|
||||
storage: &S,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
apis: Vec<EcashApiClient>,
|
||||
) -> Result<(), BandwidthControllerError>
|
||||
where
|
||||
S: Storage,
|
||||
<S as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
log::info!("Getting expiration date signatures");
|
||||
// this will also persist the signatures in the storage if they were not there already
|
||||
get_expiration_date_signatures(storage, epoch_id, expiration_date, apis.clone()).await?;
|
||||
|
||||
log::info!("Getting coin indices signatures");
|
||||
// this will also persist the signatures in the storage if they were not there already
|
||||
get_coin_index_signatures(storage, epoch_id, apis).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_ticket_book<C, St>(
|
||||
issuance_data: &IssuanceTicketBook,
|
||||
client: &C,
|
||||
storage: &St,
|
||||
) -> Result<(), BandwidthControllerError>
|
||||
apis: Option<Vec<EcashApiClient>>,
|
||||
) -> Result<IssuedTicketBook, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
St: Storage,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
// temporary
|
||||
assert!(state.voucher.typ().is_voucher());
|
||||
|
||||
let epoch_id = client.get_current_epoch().await?.epoch_id;
|
||||
let threshold = client
|
||||
.get_current_epoch_threshold()
|
||||
.await?
|
||||
.ok_or(BandwidthControllerError::NoThreshold)?;
|
||||
|
||||
let coconut_api_clients = all_coconut_api_clients(client, epoch_id).await?;
|
||||
|
||||
let signature =
|
||||
obtain_aggregate_signature(&state.voucher, &coconut_api_clients, threshold).await?;
|
||||
let issued = state.voucher.to_issued_credential(signature, epoch_id);
|
||||
|
||||
// make sure the data gets zeroized after persisting it
|
||||
let credential_data = Zeroizing::new(issued.pack_v1());
|
||||
let storable = StorableIssuedCredential {
|
||||
serialization_revision: issued.current_serialization_revision(),
|
||||
credential_data: credential_data.as_ref(),
|
||||
credential_type: issued.typ().to_string(),
|
||||
epoch_id: epoch_id
|
||||
.try_into()
|
||||
.expect("our epoch is has run over u32::MAX!"),
|
||||
let apis = match apis {
|
||||
Some(apis) => apis,
|
||||
None => all_ecash_api_clients(client, epoch_id).await?,
|
||||
};
|
||||
|
||||
log::info!("Querying wallet signatures");
|
||||
let wallet = obtain_aggregate_wallet(issuance_data, &apis, threshold).await?;
|
||||
info!("managed to obtain sufficient number of partial signatures!");
|
||||
|
||||
log::info!("Getting expiration date signatures");
|
||||
// this will also persist the signatures in the storage if they were not there already
|
||||
get_expiration_date_signatures(
|
||||
storage,
|
||||
epoch_id,
|
||||
issuance_data.expiration_date(),
|
||||
apis.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
log::info!("Getting coin indices signatures");
|
||||
// this will also persist the signatures in the storage if they were not there already
|
||||
get_coin_index_signatures(storage, epoch_id, apis).await?;
|
||||
|
||||
let issued = issuance_data.to_issued_ticketbook(wallet, epoch_id);
|
||||
|
||||
info!("persisting the ticketbook into the storage...");
|
||||
storage
|
||||
.insert_issued_credential(storable)
|
||||
.insert_issued_ticketbook(&issued)
|
||||
.await
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?;
|
||||
Ok(issued)
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials::coconut::bandwidth::IssuanceBandwidthCredential;
|
||||
|
||||
pub struct State {
|
||||
pub voucher: IssuanceBandwidthCredential,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(voucher: IssuanceBandwidthCredential) -> Self {
|
||||
State { voucher }
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_coconut::CoconutError;
|
||||
use nym_credential_storage::error::StorageError;
|
||||
use nym_credentials::error::Error as CredentialsError;
|
||||
use nym_credentials_interface::CompactEcashError;
|
||||
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
|
||||
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use nym_validator_client::coconut::CoconutApiError;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::error::ValidatorClientError;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -16,7 +16,7 @@ pub enum BandwidthControllerError {
|
||||
Nyxd(#[from] nym_validator_client::nyxd::error::NyxdError),
|
||||
|
||||
#[error("coconut api query failure: {0}")]
|
||||
CoconutApiError(#[from] CoconutApiError),
|
||||
CoconutApiError(#[from] EcashApiError),
|
||||
|
||||
#[error("There was a credential storage error - {0}")]
|
||||
CredentialStorageError(Box<dyn std::error::Error + Send + Sync>),
|
||||
@@ -28,8 +28,8 @@ pub enum BandwidthControllerError {
|
||||
#[error(transparent)]
|
||||
StorageError(#[from] StorageError),
|
||||
|
||||
#[error("Coconut error - {0}")]
|
||||
CoconutError(#[from] CoconutError),
|
||||
#[error("Ecash error - {0}")]
|
||||
EcashError(#[from] CompactEcashError),
|
||||
|
||||
#[error("Validator client error - {0}")]
|
||||
ValidatorError(#[from] ValidatorClientError),
|
||||
@@ -51,4 +51,15 @@ pub enum BandwidthControllerError {
|
||||
|
||||
#[error("can't handle recovering storage with revision {stored}. {expected} was expected")]
|
||||
UnsupportedCredentialStorageRevision { stored: u8, expected: u8 },
|
||||
|
||||
#[error("did not receive a valid response for aggregated data ({typ}) from ANY nym-api")]
|
||||
ExhaustedApiQueries { typ: String },
|
||||
}
|
||||
|
||||
impl BandwidthControllerError {
|
||||
pub fn credential_storage_error(
|
||||
source: impl std::error::Error + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
BandwidthControllerError::CredentialStorageError(Box::new(source))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// See other comments for other TaskStatus message enumds about abusing the Error trait when we
|
||||
// should have a new trait for TaskStatus messages
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum BandwidthStatusMessage {
|
||||
#[error("remaining bandwidth: {0}")]
|
||||
RemainingBandwidth(i64),
|
||||
|
||||
#[error("no bandwidth left")]
|
||||
NoBandwidth,
|
||||
}
|
||||
@@ -1,21 +1,32 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
#![warn(clippy::todo)]
|
||||
#![warn(clippy::dbg_macro)]
|
||||
|
||||
use crate::error::BandwidthControllerError;
|
||||
use crate::utils::stored_credential_to_issued_bandwidth;
|
||||
use log::{debug, error, warn};
|
||||
use crate::utils::{
|
||||
get_aggregate_verification_key, get_coin_index_signatures, get_expiration_date_signatures,
|
||||
ApiClientsWrapper,
|
||||
};
|
||||
use log::error;
|
||||
use nym_credential_storage::models::RetrievedTicketbook;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant;
|
||||
use nym_credentials::coconut::bandwidth::CredentialSpendingData;
|
||||
use nym_credentials::coconut::utils::obtain_aggregate_verification_key;
|
||||
use nym_credentials::IssuedBandwidthCredential;
|
||||
use nym_credentials_interface::VerificationKey;
|
||||
use nym_validator_client::coconut::all_coconut_api_clients;
|
||||
use nym_credentials::ecash::bandwidth::CredentialSpendingData;
|
||||
use nym_credentials_interface::{
|
||||
AnnotatedCoinIndexSignature, AnnotatedExpirationDateSignature, NymPayInfo, VerificationKeyAuth,
|
||||
};
|
||||
use nym_ecash_time::Date;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
|
||||
pub use event::BandwidthStatusMessage;
|
||||
|
||||
pub mod acquire;
|
||||
pub mod error;
|
||||
mod event;
|
||||
mod utils;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -32,13 +43,20 @@ pub struct PreparedCredential {
|
||||
/// could use correct verification key for validation.
|
||||
pub epoch_id: EpochId,
|
||||
|
||||
/// The database id of the stored credential.
|
||||
pub credential_id: i64,
|
||||
/// Auxiliary metadata associated with the withdrawn credential
|
||||
pub metadata: PreparedCredentialMetadata,
|
||||
}
|
||||
|
||||
pub struct RetrievedCredential {
|
||||
pub credential: IssuedBandwidthCredential,
|
||||
pub credential_id: i64,
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PreparedCredentialMetadata {
|
||||
/// The database id of the stored credential.
|
||||
pub ticketbook_id: i64,
|
||||
|
||||
/// The number of tickets withdrawn in this credential
|
||||
pub tickets_withdrawn: u32,
|
||||
|
||||
/// The amount of tickets used INCLUDING those tickets that JUST got withdrawn
|
||||
pub used_tickets: u32,
|
||||
}
|
||||
|
||||
impl<C, St: Storage> BandwidthController<C, St> {
|
||||
@@ -47,111 +65,155 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
}
|
||||
|
||||
/// Tries to retrieve one of the stored, unused credentials that hasn't yet expired.
|
||||
/// It marks any retrieved intermediate credentials as expired.
|
||||
pub async fn get_next_usable_credential(
|
||||
pub async fn get_next_usable_ticketbook(
|
||||
&self,
|
||||
gateway_id: &str,
|
||||
) -> Result<RetrievedCredential, BandwidthControllerError>
|
||||
tickets: u32,
|
||||
) -> Result<RetrievedTicketbook, BandwidthControllerError>
|
||||
where
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
loop {
|
||||
let Some(maybe_next) = self
|
||||
.storage
|
||||
.get_next_unspent_credential(gateway_id)
|
||||
.await
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?
|
||||
else {
|
||||
return Err(BandwidthControllerError::NoCredentialsAvailable);
|
||||
};
|
||||
let id = maybe_next.id;
|
||||
let Some(ticketbook) = self
|
||||
.storage
|
||||
.get_next_unspent_usable_ticketbook(tickets)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)?
|
||||
else {
|
||||
return Err(BandwidthControllerError::NoCredentialsAvailable);
|
||||
};
|
||||
|
||||
// try to deserialize it
|
||||
let valid_credential = match stored_credential_to_issued_bandwidth(maybe_next) {
|
||||
// check if it has already expired
|
||||
Ok(credential) => match credential.variant_data() {
|
||||
BandwidthCredentialIssuedDataVariant::Voucher(_) => {
|
||||
debug!("credential {id} is a bandwidth voucher");
|
||||
credential
|
||||
}
|
||||
BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => {
|
||||
debug!("credential {id} is a free pass");
|
||||
if freepass_info.expired() {
|
||||
warn!("the free pass (id: {id}) has already expired! The expiration was set to {}", freepass_info.expiry_date());
|
||||
self.storage.mark_expired(id).await.map_err(|err| {
|
||||
BandwidthControllerError::CredentialStorageError(Box::new(err))
|
||||
})?;
|
||||
continue;
|
||||
}
|
||||
credential
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
error!("failed to deserialize credential with id {id}: {err}. it may need to be manually removed from the storage");
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
return Ok(RetrievedCredential {
|
||||
credential: valid_credential,
|
||||
credential_id: id,
|
||||
});
|
||||
}
|
||||
Ok(ticketbook)
|
||||
}
|
||||
|
||||
pub fn storage(&self) -> &St {
|
||||
&self.storage
|
||||
pub async fn attempt_revert_ticket_usage(
|
||||
&self,
|
||||
info: PreparedCredentialMetadata,
|
||||
) -> Result<bool, BandwidthControllerError>
|
||||
where
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
self.storage
|
||||
.attempt_revert_ticketbook_withdrawal(
|
||||
info.ticketbook_id,
|
||||
info.used_tickets,
|
||||
info.tickets_withdrawn,
|
||||
)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)
|
||||
}
|
||||
|
||||
async fn get_aggregate_verification_key(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<VerificationKey, BandwidthControllerError>
|
||||
apis: &mut ApiClientsWrapper,
|
||||
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let coconut_api_clients = all_coconut_api_clients(&self.client, epoch_id).await?;
|
||||
Ok(obtain_aggregate_verification_key(&coconut_api_clients)?)
|
||||
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
|
||||
get_aggregate_verification_key(&self.storage, epoch_id, ecash_apis).await
|
||||
}
|
||||
|
||||
pub async fn prepare_bandwidth_credential(
|
||||
async fn get_coin_index_signatures(
|
||||
&self,
|
||||
gateway_id: &str,
|
||||
epoch_id: EpochId,
|
||||
apis: &mut ApiClientsWrapper,
|
||||
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
|
||||
get_coin_index_signatures(&self.storage, epoch_id, ecash_apis).await
|
||||
}
|
||||
|
||||
async fn get_expiration_date_signatures(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
apis: &mut ApiClientsWrapper,
|
||||
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
|
||||
get_expiration_date_signatures(&self.storage, epoch_id, expiration_date, ecash_apis).await
|
||||
}
|
||||
|
||||
async fn prepare_ecash_ticket_inner(
|
||||
&self,
|
||||
provider_pk: [u8; 32],
|
||||
tickets_to_spend: u32,
|
||||
mut retrieved_ticketbook: RetrievedTicketbook,
|
||||
) -> Result<CredentialSpendingData, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
|
||||
let expiration_date = retrieved_ticketbook.ticketbook.expiration_date();
|
||||
let mut api_clients = Default::default();
|
||||
|
||||
let verification_key = self
|
||||
.get_aggregate_verification_key(epoch_id, &mut api_clients)
|
||||
.await?;
|
||||
let expiration_signatures = self
|
||||
.get_expiration_date_signatures(epoch_id, expiration_date, &mut api_clients)
|
||||
.await?;
|
||||
let coin_indices_signatures = self
|
||||
.get_coin_index_signatures(epoch_id, &mut api_clients)
|
||||
.await?;
|
||||
|
||||
let pay_info = NymPayInfo::generate(provider_pk);
|
||||
|
||||
let spend_request = retrieved_ticketbook.ticketbook.prepare_for_spending(
|
||||
&verification_key,
|
||||
pay_info.into(),
|
||||
&coin_indices_signatures,
|
||||
&expiration_signatures,
|
||||
tickets_to_spend as u64,
|
||||
)?;
|
||||
Ok(spend_request)
|
||||
}
|
||||
|
||||
pub async fn prepare_ecash_ticket(
|
||||
&self,
|
||||
provider_pk: [u8; 32],
|
||||
tickets_to_spend: u32,
|
||||
) -> Result<PreparedCredential, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let retrieved_credential = self.get_next_usable_credential(gateway_id).await?;
|
||||
let retrieved_ticketbook = self.get_next_usable_ticketbook(tickets_to_spend).await?;
|
||||
|
||||
let epoch_id = retrieved_credential.credential.epoch_id();
|
||||
let credential_id = retrieved_credential.credential_id;
|
||||
let ticketbook_id = retrieved_ticketbook.ticketbook_id;
|
||||
let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
|
||||
|
||||
let verification_key = self.get_aggregate_verification_key(epoch_id).await?;
|
||||
let used_tickets =
|
||||
retrieved_ticketbook.ticketbook.spent_tickets() as u32 + tickets_to_spend;
|
||||
let metadata = PreparedCredentialMetadata {
|
||||
ticketbook_id,
|
||||
tickets_withdrawn: tickets_to_spend,
|
||||
used_tickets,
|
||||
};
|
||||
|
||||
let spend_request = retrieved_credential
|
||||
.credential
|
||||
.prepare_for_spending(&verification_key)?;
|
||||
|
||||
Ok(PreparedCredential {
|
||||
data: spend_request,
|
||||
epoch_id,
|
||||
credential_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn consume_credential(
|
||||
&self,
|
||||
id: i64,
|
||||
gateway_id: &str,
|
||||
) -> Result<(), BandwidthControllerError>
|
||||
where
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
self.storage
|
||||
.consume_coconut_credential(id, gateway_id)
|
||||
match self
|
||||
.prepare_ecash_ticket_inner(provider_pk, tickets_to_spend, retrieved_ticketbook)
|
||||
.await
|
||||
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
|
||||
{
|
||||
Ok(data) => Ok(PreparedCredential {
|
||||
data,
|
||||
epoch_id,
|
||||
metadata,
|
||||
}),
|
||||
Err(err) => {
|
||||
error!("failed to prepare credential spending request. attempting to revert withdrawal...");
|
||||
self.attempt_revert_ticket_usage(metadata).await?;
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,21 +2,180 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::BandwidthControllerError;
|
||||
use nym_credential_storage::models::StoredIssuedCredential;
|
||||
use nym_credentials::coconut::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
|
||||
use nym_credentials::coconut::bandwidth::IssuedBandwidthCredential;
|
||||
use log::warn;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials_interface::{
|
||||
AnnotatedCoinIndexSignature, AnnotatedExpirationDateSignature, VerificationKeyAuth,
|
||||
};
|
||||
use nym_ecash_time::Date;
|
||||
use nym_validator_client::coconut::all_ecash_api_clients;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::fmt::Display;
|
||||
use std::future::Future;
|
||||
|
||||
pub fn stored_credential_to_issued_bandwidth(
|
||||
cred: StoredIssuedCredential,
|
||||
) -> Result<IssuedBandwidthCredential, BandwidthControllerError> {
|
||||
if cred.serialization_revision != CURRENT_SERIALIZATION_REVISION {
|
||||
return Err(
|
||||
BandwidthControllerError::UnsupportedCredentialStorageRevision {
|
||||
stored: cred.serialization_revision,
|
||||
expected: CURRENT_SERIALIZATION_REVISION,
|
||||
},
|
||||
);
|
||||
// it really doesn't need the RwLock because it's never moved across tasks,
|
||||
// but we need all the Send/Sync action
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ApiClientsWrapper(Option<Vec<EcashApiClient>>);
|
||||
|
||||
impl ApiClientsWrapper {
|
||||
pub(crate) async fn get_or_init<C>(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
dkg_client: &C,
|
||||
) -> Result<Vec<EcashApiClient>, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
{
|
||||
if let Some(cached) = &self.0 {
|
||||
return Ok(cached.clone());
|
||||
}
|
||||
|
||||
let clients = all_ecash_api_clients(dkg_client, epoch_id).await?;
|
||||
|
||||
// technically we don't have to be cloning all the clients here, but it's way simpler than
|
||||
// dealing with locking and whatnot given the performance penalty is negligible
|
||||
self.0 = Some(clients.clone());
|
||||
Ok(clients)
|
||||
}
|
||||
|
||||
Ok(IssuedBandwidthCredential::unpack_v1(&cred.credential_data)?)
|
||||
}
|
||||
|
||||
pub(crate) async fn query_random_apis_until_success<F, T, U, E>(
|
||||
mut apis: Vec<EcashApiClient>,
|
||||
f: F,
|
||||
typ: impl Into<String>,
|
||||
) -> Result<T, BandwidthControllerError>
|
||||
where
|
||||
F: Fn(EcashApiClient) -> U,
|
||||
U: Future<Output = Result<T, E>>,
|
||||
E: Display,
|
||||
{
|
||||
// try apis in pseudorandom way to remove any bias towards the first registered dealer
|
||||
apis.shuffle(&mut thread_rng());
|
||||
|
||||
for api in apis {
|
||||
let disp = api.to_string();
|
||||
match f(api).await {
|
||||
Ok(res) => return Ok(res),
|
||||
Err(err) => {
|
||||
warn!("failed to obtain valid response from API {disp}: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(BandwidthControllerError::ExhaustedApiQueries { typ: typ.into() })
|
||||
}
|
||||
|
||||
pub(crate) async fn get_aggregate_verification_key<St>(
|
||||
storage: &St,
|
||||
epoch_id: EpochId,
|
||||
ecash_apis: Vec<EcashApiClient>,
|
||||
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
||||
where
|
||||
St: Storage,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
if let Some(stored) = storage
|
||||
.get_master_verification_key(epoch_id)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)?
|
||||
{
|
||||
return Ok(stored);
|
||||
};
|
||||
|
||||
let master_vk = query_random_apis_until_success(
|
||||
ecash_apis,
|
||||
|api| async move { api.api_client.master_verification_key(Some(epoch_id)).await },
|
||||
format!("aggregated verification key for epoch {epoch_id}"),
|
||||
)
|
||||
.await?
|
||||
.key;
|
||||
|
||||
// store the retrieved key
|
||||
storage
|
||||
.insert_master_verification_key(epoch_id, &master_vk)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)?;
|
||||
|
||||
Ok(master_vk)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_coin_index_signatures<St>(
|
||||
storage: &St,
|
||||
epoch_id: EpochId,
|
||||
ecash_apis: Vec<EcashApiClient>,
|
||||
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
||||
where
|
||||
St: Storage,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
if let Some(stored) = storage
|
||||
.get_coin_index_signatures(epoch_id)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)?
|
||||
{
|
||||
return Ok(stored);
|
||||
};
|
||||
|
||||
let index_sigs = query_random_apis_until_success(
|
||||
ecash_apis,
|
||||
|api| async move {
|
||||
api.api_client
|
||||
.global_coin_indices_signatures(Some(epoch_id))
|
||||
.await
|
||||
},
|
||||
format!("aggregated coin index signatures for epoch {epoch_id}"),
|
||||
)
|
||||
.await?
|
||||
.signatures;
|
||||
|
||||
// store the retrieved key
|
||||
storage
|
||||
.insert_coin_index_signatures(epoch_id, &index_sigs)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)?;
|
||||
|
||||
Ok(index_sigs)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_expiration_date_signatures<St>(
|
||||
storage: &St,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
ecash_apis: Vec<EcashApiClient>,
|
||||
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
||||
where
|
||||
St: Storage,
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
if let Some(stored) = storage
|
||||
.get_expiration_date_signatures(expiration_date)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)?
|
||||
{
|
||||
return Ok(stored);
|
||||
};
|
||||
|
||||
let expiration_sigs = query_random_apis_until_success(
|
||||
ecash_apis,
|
||||
|api| async move {
|
||||
api.api_client
|
||||
.global_expiration_date_signatures(Some(expiration_date))
|
||||
.await
|
||||
},
|
||||
format!("aggregated coin index signatures for date {expiration_date}"),
|
||||
)
|
||||
.await?
|
||||
.signatures;
|
||||
|
||||
// store the retrieved key
|
||||
storage
|
||||
.insert_expiration_date_signatures(epoch_id, expiration_date, &expiration_sigs)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)?;
|
||||
|
||||
Ok(expiration_sigs)
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
const-str = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
clap_complete = { workspace = true }
|
||||
clap_complete_fig = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"], optional = true }
|
||||
clap_complete = { workspace = true, optional = true }
|
||||
clap_complete_fig = { workspace = true, optional = true }
|
||||
log = { workspace = true }
|
||||
pretty_env_logger = { workspace = true }
|
||||
semver = "0.11"
|
||||
@@ -34,7 +34,7 @@ vergen = { workspace = true, features = ["build", "git", "gitcl", "rustc", "carg
|
||||
[features]
|
||||
default = []
|
||||
openapi = ["utoipa"]
|
||||
output_format = ["serde_json"]
|
||||
output_format = ["serde_json", "dep:clap"]
|
||||
bin_info_schema = ["schemars"]
|
||||
basic_tracing = ["tracing-subscriber"]
|
||||
tracing = [
|
||||
@@ -44,3 +44,4 @@ tracing = [
|
||||
"tracing-opentelemetry",
|
||||
"opentelemetry",
|
||||
]
|
||||
clap = [ "dep:clap", "dep:clap_complete", "dep:clap_complete_fig" ]
|
||||
|
||||
@@ -44,6 +44,10 @@ pub struct BinaryBuildInformation {
|
||||
/// Provides the cargo debug mode that was used for the build.
|
||||
// NOTE: keep the old name cargo_profile instead of cargo_debug for backwards compatibility
|
||||
pub cargo_profile: &'static str,
|
||||
|
||||
// VERGEN_CARGO_TARGET_TRIPLE
|
||||
/// Provides the cargo target triple that was used for the build.
|
||||
pub cargo_triple: &'static str,
|
||||
}
|
||||
|
||||
impl BinaryBuildInformation {
|
||||
@@ -66,6 +70,7 @@ impl BinaryBuildInformation {
|
||||
rustc_version: env!("VERGEN_RUSTC_SEMVER"),
|
||||
rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
|
||||
cargo_profile,
|
||||
cargo_triple: env!("VERGEN_CARGO_TARGET_TRIPLE"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +100,7 @@ impl BinaryBuildInformation {
|
||||
rustc_version: env!("VERGEN_RUSTC_SEMVER"),
|
||||
rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
|
||||
cargo_profile,
|
||||
cargo_triple: env!("VERGEN_CARGO_TARGET_TRIPLE"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +115,7 @@ impl BinaryBuildInformation {
|
||||
rustc_version: self.rustc_version.to_owned(),
|
||||
rustc_channel: self.rustc_channel.to_owned(),
|
||||
cargo_profile: self.cargo_profile.to_owned(),
|
||||
cargo_triple: self.cargo_triple.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +163,15 @@ pub struct BinaryBuildInformationOwned {
|
||||
/// Provides the cargo debug mode that was used for the build.
|
||||
// NOTE: keep the old name cargo_profile instead of cargo_debug for backwards compatibility
|
||||
pub cargo_profile: String,
|
||||
|
||||
// VERGEN_CARGO_TARGET_TRIPLE
|
||||
/// Provides the cargo target triple that was used for the build.
|
||||
#[serde(default = "unknown")]
|
||||
pub cargo_triple: String,
|
||||
}
|
||||
|
||||
fn unknown() -> String {
|
||||
"unknown".to_string()
|
||||
}
|
||||
|
||||
impl Display for BinaryBuildInformationOwned {
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod build_information;
|
||||
pub mod completions;
|
||||
pub mod logging;
|
||||
pub mod version_checker;
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
pub mod completions;
|
||||
|
||||
#[cfg(feature = "output_format")]
|
||||
pub mod output_format;
|
||||
|
||||
@@ -10,17 +10,18 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
base64 = "0.21.2"
|
||||
base64 = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
cfg-if = { workspace = true }
|
||||
clap = { workspace = true, optional = true }
|
||||
comfy-table = { version = "7.1.1", optional = true }
|
||||
futures = { workspace = true }
|
||||
humantime-serde = { workspace = true }
|
||||
log = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = "0.10.6"
|
||||
sha2 = { workspace = true }
|
||||
si-scale = { workspace = true }
|
||||
tap = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
@@ -37,7 +38,7 @@ nym-country-group = { path = "../country-group" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
|
||||
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
||||
nym-gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-metrics = { path = "../nym-metrics" }
|
||||
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
@@ -45,11 +46,15 @@ nym-pemstore = { path = "../pemstore" }
|
||||
nym-topology = { path = "../topology", features = ["serializable"] }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-task = { path = "../task" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-client-core-config-types = { path = "./config-types", features = ["disk-persistence"] }
|
||||
nym-client-core-config-types = { path = "./config-types", features = [
|
||||
"disk-persistence",
|
||||
] }
|
||||
nym-client-core-surb-storage = { path = "./surb-storage" }
|
||||
nym-client-core-gateways-storage = { path = "./gateways-storage" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
|
||||
### For serving prometheus metrics
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.hyper]
|
||||
@@ -112,7 +117,8 @@ tempfile = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
cli = ["clap"]
|
||||
cli = ["clap", "comfy-table"]
|
||||
fs-credentials-storage = ["nym-credential-storage/persistent-storage"]
|
||||
fs-surb-storage = ["nym-client-core-surb-storage/fs-surb-storage"]
|
||||
fs-gateways-storage = ["nym-client-core-gateways-storage/fs-gateways-storage"]
|
||||
wasm = ["nym-gateway-client/wasm"]
|
||||
|
||||
@@ -18,7 +18,7 @@ url.workspace = true
|
||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
|
||||
nym-crypto = { path = "../../crypto", features = ["asymmetric"] }
|
||||
nym-gateway-requests = { path = "../../../gateway/gateway-requests" }
|
||||
nym-gateway-requests = { path = "../../gateway-requests" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
workspace = true
|
||||
@@ -27,7 +27,12 @@ optional = true
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
|
||||
[features]
|
||||
fs-gateways-storage = ["sqlx"]
|
||||
fs-gateways-storage = ["sqlx"]
|
||||
|
||||
@@ -16,6 +16,7 @@ use log::info;
|
||||
use nym_client_core_gateways_storage::GatewayDetails;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::UserAgent;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg_attr(feature = "cli", derive(clap::Args))]
|
||||
@@ -60,7 +61,10 @@ pub struct CommonClientAddGatewayArgs {
|
||||
pub custom_mixnet: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub async fn add_gateway<C, A>(args: A) -> Result<GatewayInfo, C::Error>
|
||||
pub async fn add_gateway<C, A>(
|
||||
args: A,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Result<GatewayInfo, C::Error>
|
||||
where
|
||||
A: AsRef<CommonClientAddGatewayArgs>,
|
||||
C: CliClient,
|
||||
@@ -111,7 +115,8 @@ where
|
||||
hardcoded_topology.get_gateways()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::current_gateways(&mut rng, &core.client.nym_api_urls).await?
|
||||
crate::init::helpers::current_gateways(&mut rng, &core.client.nym_api_urls, user_agent)
|
||||
.await?
|
||||
};
|
||||
|
||||
// since we're registering with a brand new gateway,
|
||||
|
||||
@@ -16,6 +16,7 @@ use log::info;
|
||||
use nym_client_core_gateways_storage::GatewayDetails;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::rngs::OsRng;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -96,6 +97,7 @@ pub struct InitResultsWithConfig<T> {
|
||||
|
||||
pub async fn initialise_client<C>(
|
||||
init_args: C::InitArgs,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Result<InitResultsWithConfig<C::Config>, C::Error>
|
||||
where
|
||||
C: InitialisableClient,
|
||||
@@ -163,7 +165,8 @@ where
|
||||
hardcoded_topology.get_gateways()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::current_gateways(&mut rng, &core.client.nym_api_urls).await?
|
||||
crate::init::helpers::current_gateways(&mut rng, &core.client.nym_api_urls, user_agent)
|
||||
.await?
|
||||
};
|
||||
|
||||
let gateway_setup = GatewaySetup::New {
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::cli_helpers::{CliClient, CliClientConfig};
|
||||
use crate::error::ClientCoreError;
|
||||
use nym_credential_storage::models::BasicTicketbookInformation;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_ecash_time::ecash_today;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::Date;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AvailableTicketbook {
|
||||
pub id: i64,
|
||||
pub typ: TicketType,
|
||||
pub expiration: Date,
|
||||
pub issued_tickets: u32,
|
||||
pub claimed_tickets: u32,
|
||||
pub ticket_size: u64,
|
||||
}
|
||||
|
||||
impl AvailableTicketbook {
|
||||
#[cfg(feature = "cli")]
|
||||
fn table_row(&self) -> comfy_table::Row {
|
||||
let ecash_today = ecash_today().date();
|
||||
|
||||
let issued = self.issued_tickets;
|
||||
let si_issued = si_scale::helpers::bibytes2((issued as u64 * self.ticket_size) as f64);
|
||||
|
||||
let claimed = self.claimed_tickets;
|
||||
let si_claimed = si_scale::helpers::bibytes2((claimed as u64 * self.ticket_size) as f64);
|
||||
|
||||
let remaining = issued - claimed;
|
||||
let si_remaining =
|
||||
si_scale::helpers::bibytes2((remaining as u64 * self.ticket_size) as f64);
|
||||
let si_size = si_scale::helpers::bibytes2(self.ticket_size as f64);
|
||||
|
||||
let expiration = if self.expiration <= ecash_today {
|
||||
comfy_table::Cell::new(format!("EXPIRED ON {}", self.expiration))
|
||||
.fg(comfy_table::Color::Red)
|
||||
.add_attribute(comfy_table::Attribute::Bold)
|
||||
} else {
|
||||
comfy_table::Cell::new(self.expiration.to_string())
|
||||
};
|
||||
|
||||
vec![
|
||||
comfy_table::Cell::new(self.id.to_string()),
|
||||
comfy_table::Cell::new(self.typ),
|
||||
expiration,
|
||||
comfy_table::Cell::new(format!("{issued} ({si_issued})")),
|
||||
comfy_table::Cell::new(format!("{claimed} ({si_claimed})")),
|
||||
comfy_table::Cell::new(format!("{remaining} ({si_remaining})")),
|
||||
comfy_table::Cell::new(si_size),
|
||||
]
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<BasicTicketbookInformation> for AvailableTicketbook {
|
||||
type Error = ClientCoreError;
|
||||
|
||||
fn try_from(value: BasicTicketbookInformation) -> Result<Self, Self::Error> {
|
||||
let typ = value
|
||||
.ticketbook_type
|
||||
.parse()
|
||||
.map_err(|_| ClientCoreError::UnknownTicketType)?;
|
||||
Ok(AvailableTicketbook {
|
||||
id: value.id,
|
||||
typ,
|
||||
expiration: value.expiration_date,
|
||||
issued_tickets: value.total_tickets,
|
||||
claimed_tickets: value.used_tickets,
|
||||
ticket_size: typ.to_repr().bandwidth_value(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct AvailableTicketbooks(Vec<AvailableTicketbook>);
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
impl std::fmt::Display for AvailableTicketbooks {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut table = comfy_table::Table::new();
|
||||
table.set_header(vec![
|
||||
"id",
|
||||
"type",
|
||||
"expiration",
|
||||
"issued tickets (bandwidth)",
|
||||
"claimed tickets (bandwidth)",
|
||||
"remaining tickets (bandwidth)",
|
||||
"ticket size",
|
||||
]);
|
||||
|
||||
for ticketbook in &self.0 {
|
||||
table.add_row(ticketbook.table_row());
|
||||
}
|
||||
|
||||
writeln!(f, "{table}")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cli", derive(clap::Args))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommonShowTicketbooksArgs {
|
||||
/// Id of client that is going to display the ticketbook information
|
||||
#[cfg_attr(feature = "cli", clap(long))]
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
pub async fn show_ticketbooks<C, A>(args: A) -> Result<AvailableTicketbooks, C::Error>
|
||||
where
|
||||
A: AsRef<CommonShowTicketbooksArgs>,
|
||||
C: CliClient,
|
||||
{
|
||||
let common_args = args.as_ref();
|
||||
let id = &common_args.id;
|
||||
|
||||
let config = C::try_load_current_config(id).await?;
|
||||
let paths = config.common_paths();
|
||||
|
||||
let credentials_store =
|
||||
nym_credential_storage::initialise_persistent_storage(&paths.credentials_database).await;
|
||||
let ticketbooks = credentials_store
|
||||
.get_ticketbooks_info()
|
||||
.await
|
||||
.map_err(|err| ClientCoreError::CredentialStoreError {
|
||||
source: Box::new(err),
|
||||
})?;
|
||||
|
||||
Ok(AvailableTicketbooks(
|
||||
ticketbooks
|
||||
.into_iter()
|
||||
.map(TryInto::<AvailableTicketbook>::try_into)
|
||||
.collect::<Result<_, _>>()?,
|
||||
))
|
||||
}
|
||||
@@ -6,6 +6,7 @@ pub mod client_import_credential;
|
||||
pub mod client_init;
|
||||
pub mod client_list_gateways;
|
||||
pub mod client_run;
|
||||
pub mod client_show_ticketbooks;
|
||||
pub mod client_switch_gateway;
|
||||
pub mod traits;
|
||||
mod types;
|
||||
|
||||
@@ -40,6 +40,7 @@ use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore};
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_client::client::config::GatewayClientConfig;
|
||||
use nym_gateway_client::{
|
||||
AcknowledgementReceiver, GatewayClient, GatewayConfig, MixnetMessageReceiver, PacketRouter,
|
||||
};
|
||||
@@ -53,7 +54,7 @@ use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender,
|
||||
use nym_task::{TaskClient, TaskHandle};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::HardcodedTopologyProvider;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, UserAgent};
|
||||
use rand::rngs::OsRng;
|
||||
use std::fmt::Debug;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
@@ -184,6 +185,7 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||
shutdown: Option<TaskClient>,
|
||||
user_agent: Option<UserAgent>,
|
||||
|
||||
setup_method: GatewaySetup,
|
||||
}
|
||||
@@ -207,6 +209,7 @@ where
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
shutdown: None,
|
||||
user_agent: None,
|
||||
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
||||
}
|
||||
}
|
||||
@@ -250,6 +253,12 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_user_agent(mut self, user_agent: UserAgent) -> Self {
|
||||
self.user_agent = Some(user_agent);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_stored_topology<P: AsRef<Path>>(
|
||||
mut self,
|
||||
file: P,
|
||||
@@ -395,6 +404,11 @@ where
|
||||
gateway_listener,
|
||||
);
|
||||
GatewayClient::new(
|
||||
GatewayClientConfig::new_default()
|
||||
.with_disabled_credentials_mode(config.client.disabled_credentials_mode)
|
||||
.with_response_timeout(
|
||||
config.debug.gateway_connection.gateway_response_timeout,
|
||||
),
|
||||
cfg,
|
||||
managed_keys.identity_keypair(),
|
||||
Some(details.derived_aes128_ctr_blake3_hmac_keys),
|
||||
@@ -402,8 +416,6 @@ where
|
||||
bandwidth_controller,
|
||||
shutdown,
|
||||
)
|
||||
.with_disabled_credentials_mode(config.client.disabled_credentials_mode)
|
||||
.with_response_timeout(config.debug.gateway_connection.gateway_response_timeout)
|
||||
};
|
||||
|
||||
gateway_client
|
||||
@@ -443,7 +455,7 @@ where
|
||||
Err(ClientCoreError::CustomGatewaySelectionExpected)
|
||||
} else {
|
||||
// and make sure to invalidate the task client so we wouldn't cause premature shutdown
|
||||
shutdown.mark_as_success();
|
||||
shutdown.disarm();
|
||||
custom_gateway_transceiver.set_packet_router(packet_router)?;
|
||||
Ok(custom_gateway_transceiver)
|
||||
};
|
||||
@@ -467,6 +479,7 @@ where
|
||||
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
config_topology: config::Topology,
|
||||
nym_api_urls: Vec<Url>,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Box<dyn TopologyProvider + Send + Sync> {
|
||||
// if no custom provider was ... provided ..., create one using nym-api
|
||||
custom_provider.unwrap_or_else(|| match config_topology.topology_structure {
|
||||
@@ -477,6 +490,7 @@ where
|
||||
},
|
||||
nym_api_urls,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
user_agent,
|
||||
)),
|
||||
config::TopologyStructure::GeoAware(group_by) => {
|
||||
Box::new(GeoAwareTopologyProvider::new(
|
||||
@@ -548,7 +562,7 @@ where
|
||||
if topology_config.disable_refreshing {
|
||||
// if we're not spawning the refresher, don't cause shutdown immediately
|
||||
info!("The topology refesher is not going to be started");
|
||||
shutdown.mark_as_success();
|
||||
shutdown.disarm();
|
||||
} else {
|
||||
// don't spawn the refresher if we don't want to be refreshing the topology.
|
||||
// only use the initial values obtained
|
||||
@@ -689,6 +703,7 @@ where
|
||||
self.custom_topology_provider.take(),
|
||||
self.config.debug.topology,
|
||||
self.config.get_nym_api_endpoints(),
|
||||
self.user_agent.clone(),
|
||||
);
|
||||
|
||||
// needs to be started as the first thing to block if required waiting for the gateway
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::{
|
||||
config::{self, disk_persistence::CommonClientPaths},
|
||||
error::ClientCoreError,
|
||||
};
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-credentials-storage"))]
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage;
|
||||
|
||||
pub use nym_client_core_gateways_storage as gateways_storage;
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
use async_trait::async_trait;
|
||||
use log::{debug, error};
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use std::fmt::Debug;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
use thiserror::Error;
|
||||
@@ -111,8 +113,9 @@ impl<C, St> RemoteGateway<C, St> {
|
||||
|
||||
impl<C, St> GatewayTransceiver for RemoteGateway<C, St>
|
||||
where
|
||||
C: Send,
|
||||
St: Send,
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
St: CredentialStorage,
|
||||
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.gateway_client.gateway_identity()
|
||||
@@ -126,8 +129,9 @@ where
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<C, St> GatewaySender for RemoteGateway<C, St>
|
||||
where
|
||||
C: Send,
|
||||
St: Send,
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
St: CredentialStorage,
|
||||
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
|
||||
self.gateway_client
|
||||
|
||||
@@ -474,13 +474,6 @@ where
|
||||
Poll::Ready(Some((real_messages, conn_id))) => {
|
||||
log::trace!("handling real_messages: size: {}", real_messages.len());
|
||||
|
||||
// This is the last step in the pipeline where we know the type of the message, so
|
||||
// lets count the number of retransmissions here.
|
||||
if conn_id == TransmissionLane::Retransmission {
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::RetransmissionQueued);
|
||||
}
|
||||
|
||||
// First store what we got for the given connection id
|
||||
self.transmission_buffer.store(&conn_id, real_messages);
|
||||
let real_next = self.pop_next_message().expect("we just added one");
|
||||
|
||||
@@ -5,6 +5,7 @@ use async_trait::async_trait;
|
||||
use log::{debug, error, warn};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::{NymTopology, NymTopologyError};
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use url::Url;
|
||||
@@ -39,14 +40,26 @@ pub(crate) struct NymApiTopologyProvider {
|
||||
}
|
||||
|
||||
impl NymApiTopologyProvider {
|
||||
pub(crate) fn new(config: Config, mut nym_api_urls: Vec<Url>, client_version: String) -> Self {
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
mut nym_api_urls: Vec<Url>,
|
||||
client_version: String,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Self {
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
let validator_client = if let Some(user_agent) = user_agent {
|
||||
nym_validator_client::client::NymApiClient::new_with_user_agent(
|
||||
nym_api_urls[0].clone(),
|
||||
user_agent,
|
||||
)
|
||||
} else {
|
||||
nym_validator_client::client::NymApiClient::new(nym_api_urls[0].clone())
|
||||
};
|
||||
|
||||
NymApiTopologyProvider {
|
||||
config,
|
||||
validator_client: nym_validator_client::client::NymApiClient::new(
|
||||
nym_api_urls[0].clone(),
|
||||
),
|
||||
validator_client,
|
||||
nym_api_urls,
|
||||
client_version,
|
||||
currently_used_api: 0,
|
||||
|
||||
@@ -63,6 +63,14 @@ pub enum ClientCoreError {
|
||||
source: Box<dyn Error + Send + Sync>,
|
||||
},
|
||||
|
||||
#[error("experienced a failure with our credentials storage: {source}")]
|
||||
CredentialStoreError {
|
||||
source: Box<dyn Error + Send + Sync>,
|
||||
},
|
||||
|
||||
#[error("the provided ticket type is invalid")]
|
||||
UnknownTicketType,
|
||||
|
||||
#[error("the gateway id is invalid - {0}")]
|
||||
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_topology::{filter::VersionFilterable, gateway, mix};
|
||||
use nym_validator_client::client::IdentityKeyRef;
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tungstenite::Message;
|
||||
@@ -45,13 +46,34 @@ const MEASUREMENTS: usize = 3;
|
||||
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
const PING_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||
|
||||
struct GatewayWithLatency<'a> {
|
||||
gateway: &'a gateway::Node,
|
||||
// The abstraction that some of these helpers use
|
||||
pub trait ConnectableGateway {
|
||||
fn identity(&self) -> &identity::PublicKey;
|
||||
fn clients_address(&self) -> String;
|
||||
fn is_wss(&self) -> bool;
|
||||
}
|
||||
|
||||
impl ConnectableGateway for gateway::Node {
|
||||
fn identity(&self) -> &identity::PublicKey {
|
||||
self.identity()
|
||||
}
|
||||
|
||||
fn clients_address(&self) -> String {
|
||||
self.clients_address()
|
||||
}
|
||||
|
||||
fn is_wss(&self) -> bool {
|
||||
self.clients_wss_port.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
struct GatewayWithLatency<'a, G: ConnectableGateway> {
|
||||
gateway: &'a G,
|
||||
latency: Duration,
|
||||
}
|
||||
|
||||
impl<'a> GatewayWithLatency<'a> {
|
||||
fn new(gateway: &'a gateway::Node, latency: Duration) -> Self {
|
||||
impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
fn new(gateway: &'a G, latency: Duration) -> Self {
|
||||
GatewayWithLatency { gateway, latency }
|
||||
}
|
||||
}
|
||||
@@ -59,11 +81,16 @@ impl<'a> GatewayWithLatency<'a> {
|
||||
pub async fn current_gateways<R: Rng>(
|
||||
rng: &mut R,
|
||||
nym_apis: &[Url],
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Result<Vec<gateway::Node>, ClientCoreError> {
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
let client = nym_validator_client::client::NymApiClient::new(nym_api.clone());
|
||||
let client = if let Some(user_agent) = user_agent {
|
||||
nym_validator_client::client::NymApiClient::new_with_user_agent(nym_api.clone(), user_agent)
|
||||
} else {
|
||||
nym_validator_client::client::NymApiClient::new(nym_api.clone())
|
||||
};
|
||||
|
||||
log::debug!("Fetching list of gateways from: {nym_api}");
|
||||
|
||||
@@ -124,11 +151,14 @@ async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
|
||||
JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure)
|
||||
}
|
||||
|
||||
async fn measure_latency(gateway: &gateway::Node) -> Result<GatewayWithLatency, ClientCoreError> {
|
||||
async fn measure_latency<G>(gateway: &G) -> Result<GatewayWithLatency<G>, ClientCoreError>
|
||||
where
|
||||
G: ConnectableGateway,
|
||||
{
|
||||
let addr = gateway.clients_address();
|
||||
trace!(
|
||||
"establishing connection to {} ({addr})...",
|
||||
gateway.identity_key,
|
||||
gateway.identity(),
|
||||
);
|
||||
let mut stream = connect(&addr).await?;
|
||||
|
||||
@@ -171,7 +201,7 @@ async fn measure_latency(gateway: &gateway::Node) -> Result<GatewayWithLatency,
|
||||
let count = results.len() as u64;
|
||||
if count == 0 {
|
||||
return Err(ClientCoreError::NoGatewayMeasurements {
|
||||
identity: gateway.identity_key.to_base58_string(),
|
||||
identity: gateway.identity().to_base58_string(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,11 +211,11 @@ async fn measure_latency(gateway: &gateway::Node) -> Result<GatewayWithLatency,
|
||||
Ok(GatewayWithLatency::new(gateway, avg))
|
||||
}
|
||||
|
||||
pub async fn choose_gateway_by_latency<R: Rng>(
|
||||
pub async fn choose_gateway_by_latency<'a, R: Rng, G: ConnectableGateway + Clone>(
|
||||
rng: &mut R,
|
||||
gateways: &[gateway::Node],
|
||||
gateways: &[G],
|
||||
must_use_tls: bool,
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
) -> Result<G, ClientCoreError> {
|
||||
let gateways = filter_by_tls(gateways, must_use_tls)?;
|
||||
|
||||
info!(
|
||||
@@ -217,21 +247,19 @@ pub async fn choose_gateway_by_latency<R: Rng>(
|
||||
|
||||
info!(
|
||||
"chose gateway {} with average latency of {:?}",
|
||||
chosen.gateway.identity_key, chosen.latency
|
||||
chosen.gateway.identity(),
|
||||
chosen.latency
|
||||
);
|
||||
|
||||
Ok(chosen.gateway.clone())
|
||||
}
|
||||
|
||||
fn filter_by_tls(
|
||||
gateways: &[gateway::Node],
|
||||
fn filter_by_tls<G: ConnectableGateway>(
|
||||
gateways: &[G],
|
||||
must_use_tls: bool,
|
||||
) -> Result<Vec<&gateway::Node>, ClientCoreError> {
|
||||
) -> Result<Vec<&G>, ClientCoreError> {
|
||||
if must_use_tls {
|
||||
let filtered = gateways
|
||||
.iter()
|
||||
.filter(|g| g.clients_wss_port.is_some())
|
||||
.collect::<Vec<_>>();
|
||||
let filtered = gateways.iter().filter(|g| g.is_wss()).collect::<Vec<_>>();
|
||||
|
||||
if filtered.is_empty() {
|
||||
return Err(ClientCoreError::NoWssGateways);
|
||||
|
||||
@@ -2,7 +2,9 @@ use std::future::Future;
|
||||
|
||||
#[cfg(all(
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "cli",
|
||||
feature = "fs-surb-storage",
|
||||
feature = "fs-credentials-storage",
|
||||
feature = "fs-gateways-storage"
|
||||
))]
|
||||
pub mod cli_helpers;
|
||||
|
||||
@@ -24,7 +24,7 @@ nym-bandwidth-controller = { path = "../../bandwidth-controller" }
|
||||
nym-credentials = { path = "../../credentials" }
|
||||
nym-credential-storage = { path = "../../credential-storage" }
|
||||
nym-crypto = { path = "../../crypto" }
|
||||
nym-gateway-requests = { path = "../../../gateway/gateway-requests" }
|
||||
nym-gateway-requests = { path = "../../gateway-requests" }
|
||||
nym-network-defaults = { path = "../../network-defaults" }
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-pemstore = { path = "../../pemstore" }
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ClientBandwidth {
|
||||
inner: Arc<ClientBandwidthInner>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ClientBandwidthInner {
|
||||
/// the actual bandwidth amount (in bytes) available
|
||||
available: AtomicI64,
|
||||
|
||||
/// defines the timestamp when the bandwidth information has been logged to the logs stream
|
||||
last_logged_ts: AtomicI64,
|
||||
|
||||
/// defines the timestamp when the bandwidth value was last updated
|
||||
last_updated_ts: AtomicI64,
|
||||
}
|
||||
|
||||
impl ClientBandwidth {
|
||||
pub(crate) fn new_empty() -> Self {
|
||||
ClientBandwidth {
|
||||
inner: Arc::new(ClientBandwidthInner {
|
||||
available: AtomicI64::new(0),
|
||||
last_logged_ts: AtomicI64::new(0),
|
||||
last_updated_ts: AtomicI64::new(0),
|
||||
}),
|
||||
}
|
||||
}
|
||||
pub(crate) fn remaining(&self) -> i64 {
|
||||
self.inner.available.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_log_bandwidth(&self, now: Option<OffsetDateTime>) {
|
||||
let last = self.last_logged();
|
||||
let now = now.unwrap_or_else(OffsetDateTime::now_utc);
|
||||
if last + Duration::from_secs(10) < now {
|
||||
self.log_bandwidth(Some(now))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn log_bandwidth(&self, now: Option<OffsetDateTime>) {
|
||||
let now = now.unwrap_or_else(OffsetDateTime::now_utc);
|
||||
|
||||
let remaining = self.remaining();
|
||||
let remaining_bi2 = bibytes2(remaining as f64);
|
||||
|
||||
if remaining < 0 {
|
||||
log::warn!("OUT OF BANDWIDTH. remaining: {remaining_bi2}");
|
||||
} else {
|
||||
log::info!("remaining bandwidth: {remaining_bi2}");
|
||||
}
|
||||
|
||||
self.inner
|
||||
.last_logged_ts
|
||||
.store(now.unix_timestamp(), Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub(crate) fn update_and_maybe_log(&self, remaining: i64) {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
self.inner.available.store(remaining, Ordering::Release);
|
||||
self.inner
|
||||
.last_updated_ts
|
||||
.store(now.unix_timestamp(), Ordering::Relaxed);
|
||||
self.maybe_log_bandwidth(Some(now))
|
||||
}
|
||||
|
||||
pub(crate) fn update_and_log(&self, remaining: i64) {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
self.inner.available.store(remaining, Ordering::Release);
|
||||
self.inner
|
||||
.last_updated_ts
|
||||
.store(now.unix_timestamp(), Ordering::Relaxed);
|
||||
self.log_bandwidth(Some(now))
|
||||
}
|
||||
|
||||
fn last_logged(&self) -> OffsetDateTime {
|
||||
// SAFETY: this value is always populated with valid timestamps
|
||||
OffsetDateTime::from_unix_timestamp(self.inner.last_logged_ts.load(Ordering::Relaxed))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::GatewayClientError;
|
||||
use nym_network_defaults::TicketTypeRepr::V1MixnetEntry;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct GatewayClientConfig {
|
||||
pub connection: Connection,
|
||||
pub bandwidth: BandwidthTickets,
|
||||
}
|
||||
|
||||
impl GatewayClientConfig {
|
||||
pub fn new_default() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_disabled_credentials_mode(mut self, disabled_credentials_mode: bool) -> Self {
|
||||
self.bandwidth.require_tickets = !disabled_credentials_mode;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_reconnection_on_failure(mut self, should_reconnect_on_failure: bool) -> Self {
|
||||
self.connection.should_reconnect_on_failure = should_reconnect_on_failure;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_response_timeout(mut self, response_timeout_duration: Duration) -> Self {
|
||||
self.connection.response_timeout_duration = response_timeout_duration;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_reconnection_attempts(mut self, reconnection_attempts: usize) -> Self {
|
||||
self.connection.reconnection_attempts = reconnection_attempts;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_reconnection_backoff(mut self, backoff: Duration) -> Self {
|
||||
self.connection.reconnection_backoff = backoff;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Connection {
|
||||
/// Specifies the timeout for gateway responses
|
||||
pub response_timeout_duration: Duration,
|
||||
|
||||
/// Specifies whether client should try to reconnect to gateway on connection failure.
|
||||
pub should_reconnect_on_failure: bool,
|
||||
|
||||
/// Specifies maximum number of attempts client will try to reconnect to gateway on failure
|
||||
/// before giving up.
|
||||
pub reconnection_attempts: usize,
|
||||
|
||||
/// Delay between each subsequent reconnection attempt.
|
||||
pub reconnection_backoff: Duration,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
// 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
|
||||
pub const DEFAULT_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
||||
pub const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
|
||||
pub const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
|
||||
}
|
||||
|
||||
impl Default for Connection {
|
||||
fn default() -> Self {
|
||||
Connection {
|
||||
response_timeout_duration: Self::DEFAULT_RESPONSE_TIMEOUT,
|
||||
should_reconnect_on_failure: true,
|
||||
reconnection_attempts: Self::DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: Self::DEFAULT_RECONNECTION_BACKOFF,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BandwidthTickets {
|
||||
/// specifies whether this client will be sending bandwidth tickets or will attempt to use 'free' testnet bandwidth instead
|
||||
pub require_tickets: bool,
|
||||
|
||||
/// specifies threshold (in bytes) under which the client should send another ticket to the gateway
|
||||
pub remaining_bandwidth_threshold: i64,
|
||||
|
||||
/// specifies threshold (in bytes) under which the client will NOT send any tickets because it got accused of double spending and got its bandwidth revoked
|
||||
/// if not specified, the client will always send tickets
|
||||
pub cutoff_remaining_bandwidth_threshold: Option<i64>,
|
||||
}
|
||||
|
||||
impl BandwidthTickets {
|
||||
// TO BE CHANGED \/
|
||||
pub const DEFAULT_REQUIRES_TICKETS: bool = false;
|
||||
|
||||
// 20% of entry ticket value
|
||||
pub const DEFAULT_REMAINING_BANDWIDTH_THRESHOLD: i64 =
|
||||
(V1MixnetEntry.bandwidth_value() / 5) as i64;
|
||||
|
||||
pub const DEFAULT_CUTOFF_REMAINING_BANDWIDTH_THRESHOLD: Option<i64> = None;
|
||||
|
||||
pub fn ensure_above_cutoff(&self, available: i64) -> Result<(), GatewayClientError> {
|
||||
if let Some(cutoff) = self.cutoff_remaining_bandwidth_threshold {
|
||||
if available < cutoff {
|
||||
let available_bi2 = bibytes2(available as f64);
|
||||
let cutoff_bi2 = bibytes2(cutoff as f64);
|
||||
return Err(GatewayClientError::BandwidthBelowCutoffValue {
|
||||
available_bi2,
|
||||
cutoff_bi2,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BandwidthTickets {
|
||||
fn default() -> Self {
|
||||
BandwidthTickets {
|
||||
require_tickets: Self::DEFAULT_REQUIRES_TICKETS,
|
||||
remaining_bandwidth_threshold: Self::DEFAULT_REMAINING_BANDWIDTH_THRESHOLD,
|
||||
cutoff_remaining_bandwidth_threshold:
|
||||
Self::DEFAULT_CUTOFF_REMAINING_BANDWIDTH_THRESHOLD,
|
||||
}
|
||||
}
|
||||
}
|
||||
+124
-147
@@ -1,17 +1,18 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::bandwidth::ClientBandwidth;
|
||||
use crate::client::config::GatewayClientConfig;
|
||||
use crate::error::GatewayClientError;
|
||||
use crate::packet_router::PacketRouter;
|
||||
pub use crate::packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
};
|
||||
use crate::socket_state::{ws_fd, PartiallyDelegated, SocketState};
|
||||
use crate::socket_state::{ws_fd, PartiallyDelegatedHandle, SocketState};
|
||||
use crate::traits::GatewayPacketRouter;
|
||||
use crate::{cleanup_socket_message, try_decrypt_binary_message};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_bandwidth_controller::{BandwidthController, BandwidthStatusMessage};
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_credentials::CredentialSpendingData;
|
||||
@@ -23,14 +24,11 @@ use nym_gateway_requests::{
|
||||
BinaryRequest, ClientControlRequest, ServerResponse, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
|
||||
CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_task::TaskClient;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tungstenite::protocol::Message;
|
||||
use url::Url;
|
||||
|
||||
@@ -48,12 +46,7 @@ use wasm_utils::websocket::JSWebsocket;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::tokio::sleep;
|
||||
|
||||
// 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_RECONNECTION_ATTEMPTS: usize = 10;
|
||||
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
|
||||
pub mod config;
|
||||
|
||||
pub struct GatewayConfig {
|
||||
pub gateway_identity: identity::PublicKey,
|
||||
@@ -79,96 +72,53 @@ impl GatewayConfig {
|
||||
}
|
||||
|
||||
// TODO: this should be refactored into a state machine that keeps track of its authentication state
|
||||
#[derive(Debug)]
|
||||
pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
pub cfg: GatewayClientConfig,
|
||||
|
||||
authenticated: bool,
|
||||
disabled_credentials_mode: bool,
|
||||
bandwidth_remaining: i64,
|
||||
bandwidth: ClientBandwidth,
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
shared_key: Option<Arc<SharedKeys>>,
|
||||
connection: SocketState,
|
||||
packet_router: PacketRouter,
|
||||
response_timeout_duration: Duration,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
|
||||
// reconnection related variables
|
||||
/// Specifies whether client should try to reconnect to gateway on connection failure.
|
||||
should_reconnect_on_failure: bool,
|
||||
/// Specifies maximum number of attempts client will try to reconnect to gateway on failure
|
||||
/// before giving up.
|
||||
reconnection_attempts: usize,
|
||||
/// Delay between each subsequent reconnection attempt.
|
||||
reconnection_backoff: Duration,
|
||||
|
||||
// currently unused (but populated)
|
||||
negotiated_protocol: Option<u8>,
|
||||
|
||||
/// Listen to shutdown messages.
|
||||
shutdown: TaskClient,
|
||||
/// Listen to shutdown messages and send notifications back to the task manager
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<C, St> GatewayClient<C, St> {
|
||||
pub fn new(
|
||||
config: GatewayConfig,
|
||||
cfg: GatewayClientConfig,
|
||||
gateway_config: GatewayConfig,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
|
||||
shared_key: Option<Arc<SharedKeys>>,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
shutdown: TaskClient,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
cfg,
|
||||
authenticated: false,
|
||||
disabled_credentials_mode: true,
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address: config.gateway_listener,
|
||||
gateway_identity: config.gateway_identity,
|
||||
bandwidth: ClientBandwidth::new_empty(),
|
||||
gateway_address: gateway_config.gateway_listener,
|
||||
gateway_identity: gateway_config.gateway_identity,
|
||||
local_identity,
|
||||
shared_key,
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
response_timeout_duration: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
bandwidth_controller,
|
||||
should_reconnect_on_failure: true,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
negotiated_protocol: None,
|
||||
shutdown,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_disabled_credentials_mode(mut self, disabled_credentials_mode: bool) -> Self {
|
||||
self.disabled_credentials_mode = disabled_credentials_mode;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_reconnection_on_failure(mut self, should_reconnect_on_failure: bool) -> Self {
|
||||
self.should_reconnect_on_failure = should_reconnect_on_failure;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_response_timeout(mut self, response_timeout_duration: Duration) -> Self {
|
||||
self.response_timeout_duration = response_timeout_duration;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_reconnection_attempts(mut self, reconnection_attempts: usize) -> Self {
|
||||
self.reconnection_attempts = reconnection_attempts;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_reconnection_backoff(mut self, backoff: Duration) -> Self {
|
||||
self.reconnection_backoff = backoff;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.gateway_identity
|
||||
}
|
||||
@@ -182,7 +132,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
pub fn remaining_bandwidth(&self) -> i64 {
|
||||
self.bandwidth_remaining
|
||||
self.bandwidth.remaining()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -258,18 +208,21 @@ impl<C, St> GatewayClient<C, St> {
|
||||
info!("Attempting gateway reconnection...");
|
||||
self.authenticated = false;
|
||||
|
||||
for i in 1..self.reconnection_attempts {
|
||||
info!("attempt {}...", i);
|
||||
for i in 1..self.cfg.connection.reconnection_attempts {
|
||||
info!("reconnection attempt {}...", i);
|
||||
if self.try_reconnect().await.is_ok() {
|
||||
info!("managed to reconnect!");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
sleep(self.reconnection_backoff).await;
|
||||
sleep(self.cfg.connection.reconnection_backoff).await;
|
||||
}
|
||||
|
||||
// final attempt (done separately to be able to return a proper error)
|
||||
info!("attempt {}", self.reconnection_attempts);
|
||||
info!(
|
||||
"reconnection attempt {}",
|
||||
self.cfg.connection.reconnection_attempts
|
||||
);
|
||||
match self.try_reconnect().await {
|
||||
Ok(_) => {
|
||||
info!("managed to reconnect!");
|
||||
@@ -278,7 +231,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
Err(err) => {
|
||||
error!(
|
||||
"failed to reconnect after {} attempts",
|
||||
self.reconnection_attempts
|
||||
self.cfg.connection.reconnection_attempts
|
||||
);
|
||||
Err(err)
|
||||
}
|
||||
@@ -294,12 +247,12 @@ impl<C, St> GatewayClient<C, St> {
|
||||
_ => return Err(GatewayClientError::ConnectionInInvalidState),
|
||||
};
|
||||
|
||||
let timeout = sleep(self.response_timeout_duration);
|
||||
let timeout = sleep(self.cfg.connection.response_timeout_duration);
|
||||
tokio::pin!(timeout);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.shutdown.recv() => {
|
||||
_ = self.task_client.recv() => {
|
||||
log::trace!("GatewayClient control response: Received shutdown");
|
||||
log::debug!("GatewayClient control response: Exiting");
|
||||
break Err(GatewayClientError::ConnectionClosedGatewayShutdown);
|
||||
@@ -463,7 +416,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
ws_stream,
|
||||
self.local_identity.as_ref(),
|
||||
self.gateway_identity,
|
||||
!self.disabled_credentials_mode,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
)
|
||||
.await
|
||||
.map_err(GatewayClientError::RegistrationFailure),
|
||||
@@ -525,7 +478,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self_address,
|
||||
encrypted_address,
|
||||
iv,
|
||||
!self.disabled_credentials_mode,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
)
|
||||
.into();
|
||||
|
||||
@@ -537,9 +490,13 @@ impl<C, St> GatewayClient<C, St> {
|
||||
} => {
|
||||
self.check_gateway_protocol(protocol_version)?;
|
||||
self.authenticated = status;
|
||||
self.bandwidth_remaining = bandwidth_remaining;
|
||||
self.bandwidth.update_and_maybe_log(bandwidth_remaining);
|
||||
|
||||
self.negotiated_protocol = protocol_version;
|
||||
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
|
||||
self.task_client.send_status_msg(Box::new(
|
||||
BandwidthStatusMessage::RemainingBandwidth(bandwidth_remaining),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
@@ -573,56 +530,74 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn claim_coconut_bandwidth(
|
||||
async fn claim_ecash_bandwidth(
|
||||
&mut self,
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
let mut rng = OsRng;
|
||||
let iv = IV::new_random(&mut rng);
|
||||
|
||||
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential_v2(
|
||||
let msg = ClientControlRequest::new_enc_ecash_credential(
|
||||
credential,
|
||||
self.shared_key.as_ref().unwrap(),
|
||||
iv,
|
||||
)
|
||||
.into();
|
||||
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
|
||||
let bandwidth_remaining = match self.send_websocket_message(msg).await? {
|
||||
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
ServerResponse::TypedError { error } => {
|
||||
Err(GatewayClientError::TypedGatewayError(error))
|
||||
}
|
||||
_ => Err(GatewayClientError::UnexpectedResponse),
|
||||
}?;
|
||||
|
||||
// TODO: create tracing span
|
||||
info!("managed to claim ecash bandwidth");
|
||||
self.bandwidth.update_and_log(bandwidth_remaining);
|
||||
|
||||
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? {
|
||||
let 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),
|
||||
}?;
|
||||
|
||||
info!("managed to claim testnet bandwidth");
|
||||
self.bandwidth.update_and_log(bandwidth_remaining);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unchecked_bandwidth_controller(&self) -> &BandwidthController<C, St> {
|
||||
self.bandwidth_controller.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError>
|
||||
where
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
St: CredentialStorage,
|
||||
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
// TODO: make it configurable
|
||||
const TICKETS_TO_SPEND: u32 = 1;
|
||||
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
if self.shared_key.is_none() {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
}
|
||||
if self.bandwidth_controller.is_none() && !self.disabled_credentials_mode {
|
||||
if self.bandwidth_controller.is_none() && self.cfg.bandwidth.require_tickets {
|
||||
return Err(GatewayClientError::NoBandwidthControllerAvailable);
|
||||
}
|
||||
|
||||
warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while");
|
||||
if self.disabled_credentials_mode {
|
||||
if !self.cfg.bandwidth.require_tickets {
|
||||
info!("The client is running in disabled credentials mode - attempting to claim bandwidth without a credential");
|
||||
return self.try_claim_testnet_bandwidth().await;
|
||||
}
|
||||
@@ -638,49 +613,52 @@ impl<C, St> GatewayClient<C, St> {
|
||||
negotiated_protocol: Some(gateway_protocol),
|
||||
});
|
||||
}
|
||||
|
||||
let gateway_id = self.gateway_identity().to_base58_string();
|
||||
|
||||
let prepared_credential = self
|
||||
.bandwidth_controller
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.prepare_bandwidth_credential(&gateway_id)
|
||||
.unchecked_bandwidth_controller()
|
||||
.prepare_ecash_ticket(self.gateway_identity.to_bytes(), TICKETS_TO_SPEND)
|
||||
.await?;
|
||||
|
||||
self.claim_coconut_bandwidth(prepared_credential.data)
|
||||
.await?;
|
||||
self.bandwidth_controller
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.consume_credential(prepared_credential.credential_id, &gateway_id)
|
||||
.await?;
|
||||
match self.claim_ecash_bandwidth(prepared_credential.data).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to claim ecash bandwidth with the gateway...: {err}");
|
||||
if err.is_ticket_replay() {
|
||||
warn!("this was due to our ticket being replayed! have you messed with the database file?")
|
||||
} else {
|
||||
// TODO: tracing span
|
||||
info!("attempting to revert ticket withdrawal...");
|
||||
self.unchecked_bandwidth_controller()
|
||||
.attempt_revert_ticket_usage(prepared_credential.metadata)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
|
||||
packets
|
||||
.iter()
|
||||
.map(|packet| packet.packet().len())
|
||||
.sum::<usize>() as i64
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn batch_send_mix_packets(
|
||||
&mut self,
|
||||
packets: Vec<MixPacket>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
) -> Result<(), GatewayClientError>
|
||||
where
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
St: CredentialStorage,
|
||||
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
debug!("Sending {} mix packets", packets.len());
|
||||
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
if self.estimate_required_bandwidth(&packets) > self.bandwidth_remaining {
|
||||
return Err(GatewayClientError::NotEnoughBandwidth(
|
||||
self.estimate_required_bandwidth(&packets),
|
||||
self.bandwidth_remaining,
|
||||
));
|
||||
let bandwidth_remaining = self.bandwidth.remaining();
|
||||
if bandwidth_remaining < self.cfg.bandwidth.remaining_bandwidth_threshold {
|
||||
self.cfg
|
||||
.bandwidth
|
||||
.ensure_above_cutoff(bandwidth_remaining)?;
|
||||
self.claim_bandwidth().await?;
|
||||
}
|
||||
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
@@ -700,7 +678,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
.batch_send_websocket_messages_without_response(messages)
|
||||
.await
|
||||
{
|
||||
if err.is_closed_connection() && self.should_reconnect_on_failure {
|
||||
if err.is_closed_connection() && self.cfg.connection.should_reconnect_on_failure {
|
||||
self.attempt_reconnection().await
|
||||
} else {
|
||||
Err(err)
|
||||
@@ -715,7 +693,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
msg: Message,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if let Err(err) = self.send_websocket_message_without_response(msg).await {
|
||||
if err.is_closed_connection() && self.should_reconnect_on_failure {
|
||||
if err.is_closed_connection() && self.cfg.connection.should_reconnect_on_failure {
|
||||
debug!("Going to attempt a reconnection");
|
||||
self.attempt_reconnection().await
|
||||
} else {
|
||||
@@ -739,19 +717,23 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// TODO: possibly make responses optional
|
||||
pub async fn send_mix_packet(
|
||||
&mut self,
|
||||
mix_packet: MixPacket,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
pub async fn send_mix_packet(&mut self, mix_packet: MixPacket) -> Result<(), GatewayClientError>
|
||||
where
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
St: CredentialStorage,
|
||||
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
if (mix_packet.packet().len() as i64) > self.bandwidth_remaining {
|
||||
return Err(GatewayClientError::NotEnoughBandwidth(
|
||||
mix_packet.packet().len() as i64,
|
||||
self.bandwidth_remaining,
|
||||
));
|
||||
let bandwidth_remaining = self.bandwidth.remaining();
|
||||
if bandwidth_remaining < self.cfg.bandwidth.remaining_bandwidth_threshold {
|
||||
self.cfg
|
||||
.bandwidth
|
||||
.ensure_above_cutoff(bandwidth_remaining)?;
|
||||
self.claim_bandwidth().await?;
|
||||
}
|
||||
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
@@ -797,7 +779,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
let partially_delegated =
|
||||
match std::mem::replace(&mut self.connection, SocketState::Invalid) {
|
||||
SocketState::Available(conn) => {
|
||||
PartiallyDelegated::split_and_listen_for_mixnet_messages(
|
||||
PartiallyDelegatedHandle::split_and_listen_for_mixnet_messages(
|
||||
*conn,
|
||||
self.packet_router.clone(),
|
||||
Arc::clone(
|
||||
@@ -805,7 +787,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
.as_ref()
|
||||
.expect("no shared key present even though we're authenticated!"),
|
||||
),
|
||||
self.shutdown.clone(),
|
||||
self.bandwidth.clone(),
|
||||
self.task_client.clone(),
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
@@ -845,10 +828,12 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.establish_connection().await?;
|
||||
}
|
||||
let shared_key = self.perform_initial_authentication().await?;
|
||||
|
||||
if self.bandwidth_remaining < REMAINING_BANDWIDTH_THRESHOLD {
|
||||
info!("Claiming more bandwidth for your tokens. This will use {} token(s) from your wallet. \
|
||||
Stop the process now if you don't want that to happen.", TOKENS_TO_BURN);
|
||||
let bandwidth_remaining = self.bandwidth.remaining();
|
||||
if bandwidth_remaining < self.cfg.bandwidth.remaining_bandwidth_threshold {
|
||||
self.cfg
|
||||
.bandwidth
|
||||
.ensure_above_cutoff(bandwidth_remaining)?;
|
||||
info!("Claiming more bandwidth with existing credentials. Stop the process now if you don't want that to happen.");
|
||||
self.claim_bandwidth().await?;
|
||||
}
|
||||
|
||||
@@ -879,26 +864,22 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
// perfectly fine here, because it's not meant to be used
|
||||
let (ack_tx, _) = mpsc::unbounded();
|
||||
let (mix_tx, _) = mpsc::unbounded();
|
||||
let shutdown = TaskClient::dummy();
|
||||
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone());
|
||||
let task_client = TaskClient::dummy();
|
||||
let packet_router = PacketRouter::new(ack_tx, mix_tx, task_client.clone());
|
||||
|
||||
GatewayClient {
|
||||
cfg: GatewayClientConfig::default().with_disabled_credentials_mode(true),
|
||||
authenticated: false,
|
||||
disabled_credentials_mode: true,
|
||||
bandwidth_remaining: 0,
|
||||
bandwidth: ClientBandwidth::new_empty(),
|
||||
gateway_address: gateway_listener.to_string(),
|
||||
gateway_identity,
|
||||
local_identity,
|
||||
shared_key: None,
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
response_timeout_duration: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
bandwidth_controller: None,
|
||||
should_reconnect_on_failure: false,
|
||||
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
|
||||
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
|
||||
negotiated_protocol: None,
|
||||
shutdown,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -906,7 +887,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
self,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
shutdown: TaskClient,
|
||||
task_client: TaskClient,
|
||||
) -> GatewayClient<C, St> {
|
||||
// invariants that can't be broken
|
||||
// (unless somebody decided to expose some field that wasn't meant to be exposed)
|
||||
@@ -915,22 +896,18 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
assert!(self.shared_key.is_some());
|
||||
|
||||
GatewayClient {
|
||||
cfg: self.cfg,
|
||||
authenticated: self.authenticated,
|
||||
disabled_credentials_mode: self.disabled_credentials_mode,
|
||||
bandwidth_remaining: self.bandwidth_remaining,
|
||||
bandwidth: self.bandwidth,
|
||||
gateway_address: self.gateway_address,
|
||||
gateway_identity: self.gateway_identity,
|
||||
local_identity: self.local_identity,
|
||||
shared_key: self.shared_key,
|
||||
connection: self.connection,
|
||||
packet_router,
|
||||
response_timeout_duration: self.response_timeout_duration,
|
||||
bandwidth_controller,
|
||||
should_reconnect_on_failure: self.should_reconnect_on_failure,
|
||||
reconnection_attempts: self.reconnection_attempts,
|
||||
reconnection_backoff: self.reconnection_backoff,
|
||||
negotiated_protocol: self.negotiated_protocol,
|
||||
shutdown,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,26 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use gloo_utils::errors::JsError;
|
||||
use nym_gateway_requests::registration::handshake::error::HandshakeError;
|
||||
use nym_gateway_requests::SimpleGatewayRequestsError;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
use tungstenite::Error as WsError;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use gloo_utils::errors::JsError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GatewayClientError {
|
||||
#[error("Connection to the gateway is not established")]
|
||||
ConnectionNotEstablished,
|
||||
|
||||
#[error("Gateway returned an error response: {0}")]
|
||||
#[error("gateway returned an error response: {0}")]
|
||||
GatewayError(String),
|
||||
|
||||
#[error("gateway returned an error response: {0}")]
|
||||
TypedGatewayError(SimpleGatewayRequestsError),
|
||||
|
||||
#[error("There was a network error: {0}")]
|
||||
NetworkError(#[from] WsError),
|
||||
|
||||
@@ -62,6 +67,12 @@ pub enum GatewayClientError {
|
||||
#[error("There are no more bandwidth credentials acquired. Please buy some more if you want to use the mixnet")]
|
||||
NoMoreBandwidthCredentials,
|
||||
|
||||
#[error("the current available bandwidth ({available_bi2}) is below the minimum cutoff threshold off {cutoff_bi2}")]
|
||||
BandwidthBelowCutoffValue {
|
||||
available_bi2: String,
|
||||
cutoff_bi2: String,
|
||||
},
|
||||
|
||||
#[error("Received an unexpected response")]
|
||||
UnexpectedResponse,
|
||||
|
||||
@@ -113,4 +124,11 @@ impl GatewayClientError {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_ticket_replay(&self) -> bool {
|
||||
match self {
|
||||
GatewayClientError::TypedGatewayError(err) => err.is_ticket_replay(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use log::warn;
|
||||
use nym_gateway_requests::BinaryResponse;
|
||||
use tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
pub use client::{GatewayClient, GatewayConfig};
|
||||
pub use client::{config::GatewayClientConfig, GatewayClient, GatewayConfig};
|
||||
pub use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
pub use packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
@@ -14,6 +14,7 @@ pub use packet_router::{
|
||||
};
|
||||
pub use traits::GatewayPacketRouter;
|
||||
|
||||
mod bandwidth;
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod packet_router;
|
||||
@@ -51,10 +52,7 @@ pub(crate) fn try_decrypt_binary_message(
|
||||
BinaryResponse::PushedMixMessage(plaintext) => Some(plaintext),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"message received from the gateway was malformed! - {:?}",
|
||||
err
|
||||
);
|
||||
warn!("message received from the gateway was malformed! - {err}",);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ impl PacketRouter {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mark_as_success(&mut self) {
|
||||
self.shutdown.mark_as_success();
|
||||
pub fn disarm(&mut self) {
|
||||
self.shutdown.disarm();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::bandwidth::ClientBandwidth;
|
||||
use crate::error::GatewayClientError;
|
||||
use crate::packet_router::PacketRouter;
|
||||
use crate::traits::GatewayPacketRouter;
|
||||
@@ -10,16 +11,13 @@ use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_gateway_requests::ServerResponse;
|
||||
use nym_gateway_requests::{ServerResponse, SimpleGatewayRequestsError};
|
||||
use nym_task::TaskClient;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tungstenite::Message;
|
||||
use tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
use si_scale::helpers::bibytes2;
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::AsRawFd;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -42,6 +40,7 @@ type WsConn = JSWebsocket;
|
||||
// by some other task, however, we can notify it to get the stream back.
|
||||
|
||||
type SplitStreamReceiver = oneshot::Receiver<Result<SplitStream<WsConn>, GatewayClientError>>;
|
||||
type SplitStreamSender = oneshot::Sender<Result<SplitStream<WsConn>, GatewayClientError>>;
|
||||
|
||||
pub(crate) fn ws_fd(_conn: &WsConn) -> Option<RawFd> {
|
||||
#[cfg(unix)]
|
||||
@@ -53,92 +52,204 @@ pub(crate) fn ws_fd(_conn: &WsConn) -> Option<RawFd> {
|
||||
None
|
||||
}
|
||||
|
||||
// disgusting? absolutely, but does the trick for now
|
||||
static LAST_LOGGED_BANDWIDTH_TS: AtomicI64 = AtomicI64::new(0);
|
||||
|
||||
fn maybe_log_bandwidth(remaining: i64) {
|
||||
// SAFETY: this value is always populated with valid timestamps
|
||||
let last =
|
||||
OffsetDateTime::from_unix_timestamp(LAST_LOGGED_BANDWIDTH_TS.load(Ordering::Relaxed))
|
||||
.unwrap();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
if last + Duration::from_secs(10) < now {
|
||||
log::info!("remaining bandwidth: {}", bibytes2(remaining as f64));
|
||||
LAST_LOGGED_BANDWIDTH_TS.store(now.unix_timestamp(), Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PartiallyDelegated {
|
||||
pub(crate) struct PartiallyDelegatedHandle {
|
||||
sink_half: SplitSink<WsConn, Message>,
|
||||
// this could have been simplified by a notify as opposed to oneshot, but let's not change what ain't broke
|
||||
delegated_stream: (SplitStreamReceiver, oneshot::Sender<()>),
|
||||
ws_fd: Option<RawFd>,
|
||||
}
|
||||
|
||||
impl PartiallyDelegated {
|
||||
fn recover_received_plaintexts(
|
||||
ws_msgs: Vec<Message>,
|
||||
shared_key: &SharedKeys,
|
||||
) -> Result<Vec<Vec<u8>>, GatewayClientError> {
|
||||
let mut plaintexts = Vec::with_capacity(ws_msgs.len());
|
||||
for ws_msg in ws_msgs {
|
||||
match ws_msg {
|
||||
Message::Binary(bin_msg) => {
|
||||
// this function decrypts the request and checks the MAC
|
||||
if let Some(plaintext) = try_decrypt_binary_message(bin_msg, shared_key) {
|
||||
plaintexts.push(plaintext)
|
||||
struct PartiallyDelegatedRouter {
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedKeys>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
|
||||
stream_return: SplitStreamSender,
|
||||
stream_return_requester: oneshot::Receiver<()>,
|
||||
}
|
||||
|
||||
impl PartiallyDelegatedRouter {
|
||||
fn new(
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedKeys>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
stream_return: SplitStreamSender,
|
||||
stream_return_requester: oneshot::Receiver<()>,
|
||||
) -> PartiallyDelegatedRouter {
|
||||
PartiallyDelegatedRouter {
|
||||
packet_router,
|
||||
shared_key,
|
||||
client_bandwidth,
|
||||
stream_return,
|
||||
stream_return_requester,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(mut self, mut split_stream: SplitStream<WsConn>, mut task_client: TaskClient) {
|
||||
let mut chunked_stream = (&mut split_stream).ready_chunks(8);
|
||||
let ret: Result<_, GatewayClientError> = loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
// received system-wide shutdown
|
||||
_ = task_client.recv() => {
|
||||
log::trace!("GatewayClient listener: Received shutdown");
|
||||
log::debug!("GatewayClient listener: Exiting");
|
||||
return;
|
||||
}
|
||||
// received request to stop the task and return the stream
|
||||
_ = &mut self.stream_return_requester => {
|
||||
log::debug!("received request to return the split ws stream");
|
||||
break Ok(())
|
||||
}
|
||||
socket_msgs = chunked_stream.next() => {
|
||||
if let Err(err) = self.handle_socket_messages(socket_msgs) {
|
||||
break Err(err)
|
||||
}
|
||||
}
|
||||
// I think that in the future we should perhaps have some sequence number system, i.e.
|
||||
// so each request/response pair can be easily identified, so that if messages are
|
||||
// not ordered (for some peculiar reason) we wouldn't lose anything.
|
||||
// This would also require NOT discarding any text responses here.
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: those can return the "send confirmations" - perhaps it should be somehow worked around?
|
||||
Message::Text(text) => {
|
||||
trace!(
|
||||
let return_res = match ret {
|
||||
Err(err) => self.stream_return.send(Err(err)),
|
||||
Ok(_) => {
|
||||
self.packet_router.disarm();
|
||||
task_client.disarm();
|
||||
self.stream_return.send(Ok(split_stream))
|
||||
}
|
||||
};
|
||||
|
||||
if return_res.is_err() {
|
||||
warn!("failed to return the split stream back on the oneshot channel")
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_socket_messages(
|
||||
&self,
|
||||
msgs: Option<Vec<Result<Message, WsError>>>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
let ws_msgs = cleanup_socket_messages(msgs)?;
|
||||
let plaintexts = self.recover_received_plaintexts(ws_msgs)?;
|
||||
if !plaintexts.is_empty() {
|
||||
self.packet_router.route_received(plaintexts)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_binary_message(&self, binary_msg: Vec<u8>) -> Result<Vec<u8>, GatewayClientError> {
|
||||
// this function decrypts the request and checks the MAC
|
||||
match try_decrypt_binary_message(binary_msg, &self.shared_key) {
|
||||
Some(plaintext) => Ok(plaintext),
|
||||
None => {
|
||||
error!("failed to decrypt and verify received message!");
|
||||
Err(GatewayClientError::MalformedResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only returns an error on **critical** failures
|
||||
fn handle_text_message(&self, text: String) -> Result<(), GatewayClientError> {
|
||||
// if we fail to deserialise the response, return a hard error. we can't handle garbage
|
||||
match ServerResponse::try_from(text).map_err(|_| GatewayClientError::MalformedResponse)? {
|
||||
ServerResponse::Send {
|
||||
remaining_bandwidth,
|
||||
} => {
|
||||
self.client_bandwidth
|
||||
.update_and_maybe_log(remaining_bandwidth);
|
||||
Ok(())
|
||||
}
|
||||
ServerResponse::Error { message } => {
|
||||
error!("[1] gateway failure: {message}");
|
||||
Err(GatewayClientError::GatewayError(message))
|
||||
}
|
||||
ServerResponse::TypedError { error } => {
|
||||
match error {
|
||||
SimpleGatewayRequestsError::OutOfBandwidth {
|
||||
required,
|
||||
available,
|
||||
} => {
|
||||
let available_bi2 = bibytes2(available as f64);
|
||||
let required_bi2 = bibytes2(required as f64);
|
||||
warn!("run out of bandwidth when attempting to send the message! we got {available_bi2} available, but needed at least {required_bi2} to send the previous message");
|
||||
self.client_bandwidth.update_and_log(available);
|
||||
// UNIMPLEMENTED: we should stop sending messages until we recover bandwidth
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
error!("[2] gateway failure: {error}");
|
||||
Err(GatewayClientError::TypedGatewayError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
let name = other.name();
|
||||
warn!("received illegal message of type '{name}' in an authenticated client");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn recover_received_plaintext(
|
||||
&self,
|
||||
message: Message,
|
||||
) -> Result<Option<Vec<u8>>, GatewayClientError> {
|
||||
match message {
|
||||
Message::Binary(bin_msg) => {
|
||||
let plaintext = self.handle_binary_message(bin_msg)?;
|
||||
Ok(Some(plaintext))
|
||||
}
|
||||
// I think that in the future we should perhaps have some sequence number system, i.e.
|
||||
// so each request/response pair can be easily identified, so that if messages are
|
||||
// not ordered (for some peculiar reason) we wouldn't lose anything.
|
||||
// This would also require NOT discarding any text responses here.
|
||||
|
||||
// TODO: those can return the "send confirmations" - perhaps it should be somehow worked around?
|
||||
Message::Text(text) => {
|
||||
trace!(
|
||||
"received a text message - probably a response to some previous query! - {text}",
|
||||
);
|
||||
match ServerResponse::try_from(text)
|
||||
.map_err(|_| GatewayClientError::MalformedResponse)?
|
||||
{
|
||||
ServerResponse::Send {
|
||||
remaining_bandwidth,
|
||||
} => maybe_log_bandwidth(remaining_bandwidth),
|
||||
ServerResponse::Error { message } => {
|
||||
error!("gateway failure: {message}");
|
||||
return Err(GatewayClientError::GatewayError(message));
|
||||
}
|
||||
other => {
|
||||
warn!(
|
||||
"received illegal message of type {} in an authenticated client",
|
||||
other.name()
|
||||
)
|
||||
}
|
||||
}
|
||||
self.handle_text_message(text)?;
|
||||
Ok(None)
|
||||
}
|
||||
_ => {
|
||||
debug!("received websocket message that's neither 'Binary' nor 'Text'. it's going to get ignored");
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
_ => continue,
|
||||
fn recover_received_plaintexts(
|
||||
&self,
|
||||
messages: Vec<Message>,
|
||||
) -> Result<Vec<Vec<u8>>, GatewayClientError> {
|
||||
let mut plaintexts = Vec::new();
|
||||
for ws_msg in messages {
|
||||
if let Some(plaintext) = self.recover_received_plaintext(ws_msg)? {
|
||||
plaintexts.push(plaintext)
|
||||
}
|
||||
}
|
||||
Ok(plaintexts)
|
||||
}
|
||||
|
||||
fn route_socket_messages(
|
||||
ws_msgs: Vec<Message>,
|
||||
packet_router: &PacketRouter,
|
||||
shared_key: &SharedKeys,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
let plaintexts = Self::recover_received_plaintexts(ws_msgs, shared_key)?;
|
||||
packet_router.route_received(plaintexts)
|
||||
}
|
||||
fn spawn(self, split_stream: SplitStream<WsConn>, task_client: TaskClient) {
|
||||
let fut = async move { self.run(split_stream, task_client).await };
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_futures::spawn_local(fut);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::spawn(fut);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartiallyDelegatedHandle {
|
||||
pub(crate) fn split_and_listen_for_mixnet_messages(
|
||||
conn: WsConn,
|
||||
mut packet_router: PacketRouter,
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedKeys>,
|
||||
mut shutdown: TaskClient,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
// when called for, it NEEDS TO yield back the stream so that we could merge it and
|
||||
// read control request responses.
|
||||
@@ -146,58 +257,18 @@ impl PartiallyDelegated {
|
||||
let (stream_sender, stream_receiver) = oneshot::channel();
|
||||
|
||||
let ws_fd = ws_fd(&conn);
|
||||
let (sink, stream) = conn.split();
|
||||
|
||||
let (sink, mut stream) = conn.split();
|
||||
PartiallyDelegatedRouter::new(
|
||||
packet_router,
|
||||
shared_key,
|
||||
client_bandwidth,
|
||||
stream_sender,
|
||||
notify_receiver,
|
||||
)
|
||||
.spawn(stream, shutdown);
|
||||
|
||||
let mixnet_receiver_future = async move {
|
||||
let mut notify_receiver = notify_receiver;
|
||||
let mut chunk_stream = (&mut stream).ready_chunks(8);
|
||||
|
||||
let ret_err = loop {
|
||||
tokio::select! {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("GatewayClient listener: Received shutdown");
|
||||
log::debug!("GatewayClient listener: Exiting");
|
||||
return;
|
||||
}
|
||||
_ = &mut notify_receiver => {
|
||||
break Ok(());
|
||||
}
|
||||
msgs = chunk_stream.next() => {
|
||||
let ws_msgs = match cleanup_socket_messages(msgs) {
|
||||
Err(err) => break Err(err),
|
||||
Ok(msgs) => msgs
|
||||
};
|
||||
|
||||
if let Err(err) = Self::route_socket_messages(ws_msgs, &packet_router, shared_key.as_ref()) {
|
||||
log::error!("Route socket messages failed: {err}");
|
||||
break Err(err)
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if match ret_err {
|
||||
Err(err) => stream_sender.send(Err(err)),
|
||||
Ok(_) => {
|
||||
packet_router.mark_as_success();
|
||||
shutdown.mark_as_success();
|
||||
stream_sender.send(Ok(stream))
|
||||
}
|
||||
}
|
||||
.is_err()
|
||||
{
|
||||
warn!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_futures::spawn_local(mixnet_receiver_future);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::spawn(mixnet_receiver_future);
|
||||
|
||||
PartiallyDelegated {
|
||||
PartiallyDelegatedHandle {
|
||||
ws_fd,
|
||||
sink_half: sink,
|
||||
delegated_stream: (stream_receiver, notify_sender),
|
||||
@@ -266,7 +337,7 @@ impl PartiallyDelegated {
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum SocketState {
|
||||
Available(Box<WsConn>),
|
||||
PartiallyDelegated(PartiallyDelegated),
|
||||
PartiallyDelegated(PartiallyDelegatedHandle),
|
||||
NotConnected,
|
||||
Invalid,
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ nym-contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common
|
||||
nym-mixnet-contract-common = { path = "../../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-contract" }
|
||||
nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
nym-ecash-contract-common = { path = "../../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
@@ -26,9 +27,10 @@ thiserror = { workspace = true }
|
||||
log = { workspace = true }
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
tokio = { workspace = true, features = ["sync", "time"] }
|
||||
time = { workspace = true, features = ["formatting"] }
|
||||
futures = { workspace = true }
|
||||
|
||||
nym-coconut = { path = "../../nymcoconut" }
|
||||
nym-compact-ecash = { path = "../../nym_offline_compact_ecash" }
|
||||
nym-network-defaults = { path = "../../network-defaults" }
|
||||
nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
|
||||
|
||||
@@ -54,7 +56,7 @@ cw-controllers = { workspace = true }
|
||||
prost = { workspace = true, default-features = false }
|
||||
flate2 = { workspace = true }
|
||||
sha2 = { version = "0.9.5" }
|
||||
itertools = { version = "0.10" }
|
||||
itertools = { workspace = true }
|
||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
cosmwasm-std = { workspace = true }
|
||||
|
||||
|
||||
@@ -8,10 +8,14 @@ use crate::{
|
||||
nym_api, DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient,
|
||||
ReqwestRpcClient, ValidatorClientError,
|
||||
};
|
||||
use nym_api_requests::coconut::models::FreePassNonceResponse;
|
||||
use nym_api_requests::coconut::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, FreePassRequest, VerifyCredentialBody,
|
||||
VerifyCredentialResponse,
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
|
||||
SpentCredentialsResponse, VerifyEcashTicketBody,
|
||||
};
|
||||
use nym_api_requests::ecash::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
||||
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
|
||||
};
|
||||
use nym_api_requests::models::{DescribedGateway, MixNodeBondAnnotated};
|
||||
use nym_api_requests::models::{
|
||||
@@ -19,7 +23,10 @@ use nym_api_requests::models::{
|
||||
RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::SkimmedNode;
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
use time::Date;
|
||||
use url::Url;
|
||||
|
||||
pub use crate::nym_api::NymApiClientExt;
|
||||
@@ -28,7 +35,7 @@ pub use nym_mixnet_contract_common::{
|
||||
};
|
||||
|
||||
// re-export the type to not break existing imports
|
||||
pub use crate::coconut::CoconutApiClient;
|
||||
pub use crate::coconut::EcashApiClient;
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::rpc::http_client;
|
||||
@@ -258,6 +265,16 @@ impl NymApiClient {
|
||||
NymApiClient { nym_api }
|
||||
}
|
||||
|
||||
pub fn new_with_user_agent(api_url: Url, user_agent: UserAgent) -> Self {
|
||||
let nym_api = nym_api::Client::builder::<_, ValidatorClientError>(api_url)
|
||||
.expect("invalid api url")
|
||||
.with_user_agent(user_agent)
|
||||
.build::<ValidatorClientError>()
|
||||
.expect("failed to build nym api client");
|
||||
|
||||
NymApiClient { nym_api }
|
||||
}
|
||||
|
||||
pub fn api_url(&self) -> &Url {
|
||||
self.nym_api.current_url()
|
||||
}
|
||||
@@ -364,24 +381,73 @@ impl NymApiClient {
|
||||
Ok(self.nym_api.blind_sign(request_body).await?)
|
||||
}
|
||||
|
||||
pub async fn verify_bandwidth_credential(
|
||||
pub async fn verify_ecash_ticket(
|
||||
&self,
|
||||
request_body: &VerifyCredentialBody,
|
||||
) -> Result<VerifyCredentialResponse, ValidatorClientError> {
|
||||
request_body: &VerifyEcashTicketBody,
|
||||
) -> Result<EcashTicketVerificationResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.verify_ecash_ticket(request_body).await?)
|
||||
}
|
||||
|
||||
pub async fn batch_redeem_ecash_tickets(
|
||||
&self,
|
||||
request_body: &BatchRedeemTicketsBody,
|
||||
) -> Result<EcashBatchTicketRedemptionResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.verify_bandwidth_credential(request_body)
|
||||
.batch_redeem_ecash_tickets(request_body)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn free_pass_nonce(&self) -> Result<FreePassNonceResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.free_pass_nonce().await?)
|
||||
pub async fn spent_credentials_filter(
|
||||
&self,
|
||||
) -> Result<SpentCredentialsResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.double_spending_filter_v1().await?)
|
||||
}
|
||||
|
||||
pub async fn issue_free_pass_credential(
|
||||
pub async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
request: &FreePassRequest,
|
||||
) -> Result<BlindedSignatureResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.free_pass(request).await?)
|
||||
expiration_date: Option<Date>,
|
||||
) -> Result<PartialExpirationDateSignatureResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.partial_expiration_date_signatures(expiration_date)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn partial_coin_indices_signatures(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<PartialCoinIndicesSignatureResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.partial_coin_indices_signatures(epoch_id)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn global_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
) -> Result<AggregatedExpirationDateSignatureResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.global_expiration_date_signatures(expiration_date)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn global_coin_indices_signatures(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<AggregatedCoinIndicesSignatureResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.global_coin_indices_signatures(epoch_id)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn master_verification_key(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<VerificationKeyResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.master_verification_key(epoch_id).await?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,26 +4,40 @@
|
||||
use crate::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::NymApiClient;
|
||||
use nym_coconut::{Base58, CoconutError, VerificationKey};
|
||||
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
|
||||
use nym_coconut_dkg_common::verification_key::ContractVKShare;
|
||||
use nym_compact_ecash::error::CompactEcashError;
|
||||
use nym_compact_ecash::{Base58, VerificationKeyAuth};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
// TODO: it really doesn't feel like this should live in this crate.
|
||||
#[derive(Clone)]
|
||||
pub struct CoconutApiClient {
|
||||
pub struct EcashApiClient {
|
||||
pub api_client: NymApiClient,
|
||||
pub verification_key: VerificationKey,
|
||||
pub verification_key: VerificationKeyAuth,
|
||||
pub node_id: NodeIndex,
|
||||
pub cosmos_address: cosmrs::AccountId,
|
||||
}
|
||||
|
||||
impl Display for EcashApiClient {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[id: {}] {} @ {}",
|
||||
self.node_id,
|
||||
self.cosmos_address,
|
||||
self.api_client.api_url()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this should be using the coconut error
|
||||
// (which is in different crate; perhaps this client should be moved there?)
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CoconutApiError {
|
||||
pub enum EcashApiError {
|
||||
// TODO: ask @BN whether this is a correct error message
|
||||
#[error("the provided key share hasn't been verified")]
|
||||
UnverifiedShare,
|
||||
@@ -43,7 +57,7 @@ pub enum CoconutApiError {
|
||||
#[error("the provided verification key is malformed: {source}")]
|
||||
MalformedVerificationKey {
|
||||
#[from]
|
||||
source: CoconutError,
|
||||
source: CompactEcashError,
|
||||
},
|
||||
|
||||
#[error("the provided account address is malformed: {source}")]
|
||||
@@ -53,29 +67,29 @@ pub enum CoconutApiError {
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<ContractVKShare> for CoconutApiClient {
|
||||
type Error = CoconutApiError;
|
||||
impl TryFrom<ContractVKShare> for EcashApiClient {
|
||||
type Error = EcashApiError;
|
||||
|
||||
fn try_from(share: ContractVKShare) -> Result<Self, Self::Error> {
|
||||
if !share.verified {
|
||||
return Err(CoconutApiError::UnverifiedShare);
|
||||
return Err(EcashApiError::UnverifiedShare);
|
||||
}
|
||||
|
||||
let url_address = Url::parse(&share.announce_address)?;
|
||||
|
||||
Ok(CoconutApiClient {
|
||||
Ok(EcashApiClient {
|
||||
api_client: NymApiClient::new(url_address),
|
||||
verification_key: VerificationKey::try_from_bs58(&share.share)?,
|
||||
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
|
||||
node_id: share.node_index,
|
||||
cosmos_address: share.owner.as_str().parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn all_coconut_api_clients<C>(
|
||||
pub async fn all_ecash_api_clients<C>(
|
||||
client: &C,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Vec<CoconutApiClient>, CoconutApiError>
|
||||
) -> Result<Vec<EcashApiClient>, EcashApiError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ValidatorClientError {
|
||||
#[error("nym api request failed - {source}")]
|
||||
#[error("nym api request failed: {source}")]
|
||||
NymAPIError {
|
||||
#[from]
|
||||
source: nym_api::error::NymAPIError,
|
||||
@@ -19,7 +19,7 @@ pub enum ValidatorClientError {
|
||||
#[error("One of the provided URLs was malformed - {0}")]
|
||||
MalformedUrlProvided(#[from] url::ParseError),
|
||||
|
||||
#[error("nyxd request failed - {0}")]
|
||||
#[error("nyxd request failed: {0}")]
|
||||
NyxdError(#[from] crate::nyxd::error::NyxdError),
|
||||
|
||||
#[error("No validator API url has been provided")]
|
||||
|
||||
@@ -15,8 +15,9 @@ pub use crate::error::ValidatorClientError;
|
||||
pub use crate::rpc::reqwest::ReqwestRpcClient;
|
||||
pub use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
pub use client::NymApiClient;
|
||||
pub use client::{Client, CoconutApiClient, Config};
|
||||
pub use client::{Client, Config, EcashApiClient};
|
||||
pub use nym_api_requests::*;
|
||||
pub use nym_http_api_client::UserAgent;
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
pub use cosmrs::rpc::HttpClient as HttpRpcClient;
|
||||
|
||||
@@ -2,16 +2,33 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nym_api::error::NymAPIError;
|
||||
use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
|
||||
use crate::nym_api::routes::{ecash, CORE_STATUS_COUNT, SINCE_ARG};
|
||||
use async_trait::async_trait;
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
|
||||
VerifyEcashTicketBody,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{CachedNodesResponse, SkimmedNode};
|
||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
use time::format_description::BorrowedFormatItem;
|
||||
use time::Date;
|
||||
|
||||
pub mod error;
|
||||
pub mod routes;
|
||||
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
pub use nym_api_requests::{
|
||||
coconut::{
|
||||
ecash::{
|
||||
models::{
|
||||
EpochCredentialsResponse, IssuedCredential, IssuedCredentialBody,
|
||||
IssuedCredentialResponse, IssuedCredentialsResponse,
|
||||
EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse,
|
||||
IssuedTicketbook, IssuedTicketbookBody, SpentCredentialsResponse,
|
||||
},
|
||||
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody,
|
||||
VerifyCredentialBody, VerifyCredentialResponse,
|
||||
PartialCoinIndicesSignatureResponse, PartialExpirationDateSignatureResponse,
|
||||
VerifyEcashCredentialBody,
|
||||
},
|
||||
models::{
|
||||
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
|
||||
@@ -22,18 +39,12 @@ pub use nym_api_requests::{
|
||||
},
|
||||
};
|
||||
pub use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
|
||||
pub mod error;
|
||||
pub mod routes;
|
||||
|
||||
use nym_api_requests::coconut::models::FreePassNonceResponse;
|
||||
use nym_api_requests::coconut::FreePassRequest;
|
||||
use nym_api_requests::nym_nodes::{CachedNodesResponse, SkimmedNode};
|
||||
pub use nym_http_api_client::Client;
|
||||
|
||||
pub fn rfc_3339_date() -> Vec<BorrowedFormatItem<'static>> {
|
||||
time::format_description::parse("[year]-[month]-[day]").unwrap()
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait NymApiClientExt: ApiClient {
|
||||
@@ -420,36 +431,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn free_pass_nonce(&self) -> Result<FreePassNonceResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_FREE_PASS_NONCE,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn free_pass(
|
||||
&self,
|
||||
request: &FreePassRequest,
|
||||
) -> Result<BlindedSignatureResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_FREE_PASS,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
@@ -457,9 +438,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_BLIND_SIGN,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_BLIND_SIGN,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
@@ -467,16 +447,15 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn verify_bandwidth_credential(
|
||||
async fn verify_ecash_ticket(
|
||||
&self,
|
||||
request_body: &VerifyCredentialBody,
|
||||
) -> Result<VerifyCredentialResponse, NymAPIError> {
|
||||
request_body: &VerifyEcashTicketBody,
|
||||
) -> Result<EcashTicketVerificationResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_VERIFY_BANDWIDTH_CREDENTIAL,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::VERIFY_ECASH_TICKET,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
@@ -484,6 +463,139 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn batch_redeem_ecash_tickets(
|
||||
&self,
|
||||
request_body: &BatchRedeemTicketsBody,
|
||||
) -> Result<EcashBatchTicketRedemptionResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::BATCH_REDEEM_ECASH_TICKETS,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn double_spending_filter_v1(&self) -> Result<SpentCredentialsResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::DOUBLE_SPENDING_FILTER_V1,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
) -> Result<PartialExpirationDateSignatureResponse, NymAPIError> {
|
||||
let params = match expiration_date {
|
||||
None => Vec::new(),
|
||||
Some(exp) => vec![(
|
||||
ecash::EXPIRATION_DATE_PARAM,
|
||||
exp.format(&rfc_3339_date()).unwrap(),
|
||||
)],
|
||||
};
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::PARTIAL_EXPIRATION_DATE_SIGNATURES,
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn partial_coin_indices_signatures(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<PartialCoinIndicesSignatureResponse, NymAPIError> {
|
||||
let params = match epoch_id {
|
||||
None => Vec::new(),
|
||||
Some(epoch_id) => vec![(ecash::EPOCH_ID_PARAM, epoch_id.to_string())],
|
||||
};
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::PARTIAL_COIN_INDICES_SIGNATURES,
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn global_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
) -> Result<AggregatedExpirationDateSignatureResponse, NymAPIError> {
|
||||
let params = match expiration_date {
|
||||
None => Vec::new(),
|
||||
Some(exp) => vec![(
|
||||
ecash::EXPIRATION_DATE_PARAM,
|
||||
exp.format(&rfc_3339_date()).unwrap(),
|
||||
)],
|
||||
};
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::GLOBAL_EXPIRATION_DATE_SIGNATURES,
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn global_coin_indices_signatures(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<AggregatedCoinIndicesSignatureResponse, NymAPIError> {
|
||||
let params = match epoch_id {
|
||||
None => Vec::new(),
|
||||
Some(epoch_id) => vec![(ecash::EPOCH_ID_PARAM, epoch_id.to_string())],
|
||||
};
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::GLOBAL_COIN_INDICES_SIGNATURES,
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn master_verification_key(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
) -> Result<VerificationKeyResponse, NymAPIError> {
|
||||
let params = match epoch_id {
|
||||
None => Vec::new(),
|
||||
Some(epoch_id) => vec![(ecash::EPOCH_ID_PARAM, epoch_id.to_string())],
|
||||
};
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ecash::MASTER_VERIFICATION_KEY,
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn epoch_credentials(
|
||||
&self,
|
||||
dkg_epoch: EpochId,
|
||||
@@ -491,9 +603,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_EPOCH_CREDENTIALS,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_EPOCH_CREDENTIALS,
|
||||
&dkg_epoch.to_string(),
|
||||
],
|
||||
NO_PARAMS,
|
||||
@@ -508,9 +619,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_ISSUED_CREDENTIAL,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_ISSUED_CREDENTIAL,
|
||||
&credential_id.to_string(),
|
||||
],
|
||||
NO_PARAMS,
|
||||
@@ -525,9 +635,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_ISSUED_CREDENTIALS,
|
||||
routes::ECASH_ROUTES,
|
||||
routes::ECASH_ISSUED_CREDENTIALS,
|
||||
],
|
||||
NO_PARAMS,
|
||||
&CredentialsRequestBody {
|
||||
|
||||
@@ -12,16 +12,27 @@ pub const DETAILED: &str = "detailed";
|
||||
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
|
||||
pub const ACTIVE: &str = "active";
|
||||
pub const REWARDED: &str = "rewarded";
|
||||
pub const COCONUT_ROUTES: &str = "coconut";
|
||||
pub const BANDWIDTH: &str = "bandwidth";
|
||||
pub const DOUBLE_SPENDING_FILTER_V1: &str = "double-spending-filter-v1";
|
||||
|
||||
pub const COCONUT_FREE_PASS: &str = "free-pass";
|
||||
pub const COCONUT_FREE_PASS_NONCE: &str = "free-pass-nonce";
|
||||
pub const COCONUT_BLIND_SIGN: &str = "blind-sign";
|
||||
pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential";
|
||||
pub const COCONUT_EPOCH_CREDENTIALS: &str = "epoch-credentials";
|
||||
pub const COCONUT_ISSUED_CREDENTIAL: &str = "issued-credential";
|
||||
pub const COCONUT_ISSUED_CREDENTIALS: &str = "issued-credentials";
|
||||
pub const ECASH_ROUTES: &str = "ecash";
|
||||
|
||||
pub use ecash::*;
|
||||
pub mod ecash {
|
||||
pub const ECASH_BLIND_SIGN: &str = "blind-sign";
|
||||
pub const VERIFY_ECASH_TICKET: &str = "verify-ecash-ticket";
|
||||
pub const BATCH_REDEEM_ECASH_TICKETS: &str = "batch-redeem-ecash-tickets";
|
||||
pub const PARTIAL_EXPIRATION_DATE_SIGNATURES: &str = "partial-expiration-date-signatures";
|
||||
pub const GLOBAL_EXPIRATION_DATE_SIGNATURES: &str = "aggregated-expiration-date-signatures";
|
||||
pub const PARTIAL_COIN_INDICES_SIGNATURES: &str = "partial-coin-indices-signatures";
|
||||
pub const GLOBAL_COIN_INDICES_SIGNATURES: &str = "aggregated-coin-indices-signatures";
|
||||
pub const MASTER_VERIFICATION_KEY: &str = "master-verification-key";
|
||||
pub const ECASH_EPOCH_CREDENTIALS: &str = "epoch-credentials";
|
||||
pub const ECASH_ISSUED_CREDENTIAL: &str = "issued-credential";
|
||||
pub const ECASH_ISSUED_CREDENTIALS: &str = "issued-credentials";
|
||||
|
||||
pub const EXPIRATION_DATE_PARAM: &str = "expiration_date";
|
||||
pub const EPOCH_ID_PARAM: &str = "epoch_id";
|
||||
}
|
||||
|
||||
pub const STATUS_ROUTES: &str = "status";
|
||||
pub const MIXNODE: &str = "mixnode";
|
||||
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::collect_paged;
|
||||
use crate::nyxd::contract_traits::NymContractsProvider;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::CosmWasmClient;
|
||||
use async_trait::async_trait;
|
||||
use nym_coconut_bandwidth_contract_common::msg::QueryMsg as CoconutBandwidthQueryMsg;
|
||||
use nym_coconut_bandwidth_contract_common::spend_credential::{
|
||||
PagedSpendCredentialResponse, SpendCredential, SpendCredentialResponse,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait CoconutBandwidthQueryClient {
|
||||
async fn query_coconut_bandwidth_contract<T>(
|
||||
&self,
|
||||
query: CoconutBandwidthQueryMsg,
|
||||
) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>;
|
||||
|
||||
async fn get_spent_credential(
|
||||
&self,
|
||||
blinded_serial_number: String,
|
||||
) -> Result<SpendCredentialResponse, NyxdError> {
|
||||
self.query_coconut_bandwidth_contract(CoconutBandwidthQueryMsg::GetSpentCredential {
|
||||
blinded_serial_number,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_all_spent_credential_paged(
|
||||
&self,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedSpendCredentialResponse, NyxdError> {
|
||||
self.query_coconut_bandwidth_contract(CoconutBandwidthQueryMsg::GetAllSpentCredentials {
|
||||
limit,
|
||||
start_after,
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait PagedCoconutBandwidthQueryClient: CoconutBandwidthQueryClient {
|
||||
async fn get_all_spent_credentials(&self) -> Result<Vec<SpendCredential>, NyxdError> {
|
||||
collect_paged!(self, get_all_spent_credential_paged, spend_credentials)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T> PagedCoconutBandwidthQueryClient for T where T: CoconutBandwidthQueryClient {}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<C> CoconutBandwidthQueryClient for C
|
||||
where
|
||||
C: CosmWasmClient + NymContractsProvider + Send + Sync,
|
||||
{
|
||||
async fn query_coconut_bandwidth_contract<T>(
|
||||
&self,
|
||||
query: CoconutBandwidthQueryMsg,
|
||||
) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
let coconut_bandwidth_contract_address = self
|
||||
.coconut_bandwidth_contract_address()
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("coconut bandwidth contract"))?;
|
||||
self.query_contract_smart(coconut_bandwidth_contract_address, &query)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
fn all_query_variants_are_covered<C: CoconutBandwidthQueryClient + Send + Sync>(
|
||||
client: C,
|
||||
msg: CoconutBandwidthQueryMsg,
|
||||
) {
|
||||
match msg {
|
||||
CoconutBandwidthQueryMsg::GetSpentCredential {
|
||||
blinded_serial_number,
|
||||
} => client.get_spent_credential(blinded_serial_number).ignore(),
|
||||
CoconutBandwidthQueryMsg::GetAllSpentCredentials { limit, start_after } => client
|
||||
.get_all_spent_credential_paged(start_after, limit)
|
||||
.ignore(),
|
||||
};
|
||||
}
|
||||
}
|
||||
-153
@@ -1,153 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nyxd::contract_traits::NymContractsProvider;
|
||||
use crate::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{Coin, Fee, SigningCosmWasmClient};
|
||||
use crate::signing::signer::OfflineSigner;
|
||||
use async_trait::async_trait;
|
||||
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialData;
|
||||
use nym_coconut_bandwidth_contract_common::{
|
||||
deposit::DepositData, msg::ExecuteMsg as CoconutBandwidthExecuteMsg,
|
||||
};
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait CoconutBandwidthSigningClient {
|
||||
async fn execute_coconut_bandwidth_contract(
|
||||
&self,
|
||||
fee: Option<Fee>,
|
||||
msg: CoconutBandwidthExecuteMsg,
|
||||
memo: String,
|
||||
funds: Vec<Coin>,
|
||||
) -> Result<ExecuteResult, NyxdError>;
|
||||
|
||||
async fn deposit(
|
||||
&self,
|
||||
amount: Coin,
|
||||
info: String,
|
||||
verification_key: String,
|
||||
encryption_key: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = CoconutBandwidthExecuteMsg::DepositFunds {
|
||||
data: DepositData::new(info, verification_key, encryption_key),
|
||||
};
|
||||
self.execute_coconut_bandwidth_contract(
|
||||
fee,
|
||||
req,
|
||||
"CoconutBandwidth::Deposit".to_string(),
|
||||
vec![amount],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn spend_credential(
|
||||
&self,
|
||||
funds: Coin,
|
||||
blinded_serial_number: String,
|
||||
gateway_cosmos_address: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = CoconutBandwidthExecuteMsg::SpendCredential {
|
||||
data: SpendCredentialData::new(
|
||||
funds.into(),
|
||||
blinded_serial_number,
|
||||
gateway_cosmos_address,
|
||||
),
|
||||
};
|
||||
self.execute_coconut_bandwidth_contract(
|
||||
fee,
|
||||
req,
|
||||
"CoconutBandwidth::SpendCredential".to_string(),
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn release_funds(
|
||||
&self,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_coconut_bandwidth_contract(
|
||||
fee,
|
||||
CoconutBandwidthExecuteMsg::ReleaseFunds {
|
||||
funds: amount.into(),
|
||||
},
|
||||
"CoconutBandwidth::ReleaseFunds".to_string(),
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<C> CoconutBandwidthSigningClient for C
|
||||
where
|
||||
C: SigningCosmWasmClient + NymContractsProvider + Sync,
|
||||
NyxdError: From<<Self as OfflineSigner>::Error>,
|
||||
{
|
||||
async fn execute_coconut_bandwidth_contract(
|
||||
&self,
|
||||
fee: Option<Fee>,
|
||||
msg: CoconutBandwidthExecuteMsg,
|
||||
memo: String,
|
||||
funds: Vec<Coin>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let coconut_bandwidth_contract_address = self
|
||||
.coconut_bandwidth_contract_address()
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("coconut bandwidth contract"))?;
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
|
||||
self.execute(
|
||||
signer_address,
|
||||
coconut_bandwidth_contract_address,
|
||||
&msg,
|
||||
fee,
|
||||
memo,
|
||||
funds,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue};
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
fn all_execute_variants_are_covered<C: CoconutBandwidthSigningClient + Send + Sync>(
|
||||
client: C,
|
||||
msg: CoconutBandwidthExecuteMsg,
|
||||
) {
|
||||
match msg {
|
||||
CoconutBandwidthExecuteMsg::DepositFunds { data } => client
|
||||
.deposit(
|
||||
mock_coin(),
|
||||
data.deposit_info().to_string(),
|
||||
data.identity_key().to_string(),
|
||||
data.encryption_key().to_string(),
|
||||
None,
|
||||
)
|
||||
.ignore(),
|
||||
CoconutBandwidthExecuteMsg::SpendCredential { data } => client
|
||||
.spend_credential(
|
||||
mock_coin(),
|
||||
data.blinded_serial_number().to_string(),
|
||||
data.gateway_cosmos_address().to_string(),
|
||||
None,
|
||||
)
|
||||
.ignore(),
|
||||
CoconutBandwidthExecuteMsg::ReleaseFunds { funds } => {
|
||||
client.release_funds(funds.into(), None).ignore()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,11 @@ pub trait DkgQueryClient {
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_epoch_threshold(&self, epoch_id: EpochId) -> Result<Option<u64>, NyxdError> {
|
||||
let request = DkgQueryMsg::GetEpochThreshold { epoch_id };
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_registered_dealer_details(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
@@ -256,6 +261,9 @@ mod tests {
|
||||
DkgQueryMsg::GetCurrentEpochThreshold {} => {
|
||||
client.get_current_epoch_threshold().ignore()
|
||||
}
|
||||
DkgQueryMsg::GetEpochThreshold { epoch_id } => {
|
||||
client.get_epoch_threshold(epoch_id).ignore()
|
||||
}
|
||||
DkgQueryMsg::GetRegisteredDealer {
|
||||
dealer_address,
|
||||
epoch_id,
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::collect_paged;
|
||||
use crate::nyxd::contract_traits::NymContractsProvider;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::CosmWasmClient;
|
||||
use async_trait::async_trait;
|
||||
use cosmwasm_std::Coin;
|
||||
use nym_ecash_contract_common::msg::QueryMsg as EcashQueryMsg;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub use nym_ecash_contract_common::blacklist::{
|
||||
BlacklistedAccount, BlacklistedAccountResponse, PagedBlacklistedAccountResponse,
|
||||
};
|
||||
pub use nym_ecash_contract_common::deposit::{
|
||||
Deposit, DepositData, DepositId, DepositResponse, PagedDepositsResponse,
|
||||
};
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait EcashQueryClient {
|
||||
async fn query_ecash_contract<T>(&self, query: EcashQueryMsg) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>;
|
||||
|
||||
async fn get_blacklisted_account(
|
||||
&self,
|
||||
public_key: String,
|
||||
) -> Result<BlacklistedAccountResponse, NyxdError> {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetBlacklistedAccount { public_key })
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_blacklist_paged(
|
||||
&self,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedBlacklistedAccountResponse, NyxdError> {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetBlacklistPaged { start_after, limit })
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_required_deposit_amount(&self) -> Result<Coin, NyxdError> {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetRequiredDepositAmount {})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_deposit(&self, deposit_id: u32) -> Result<DepositResponse, NyxdError> {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetDeposit { deposit_id })
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_deposits_paged(
|
||||
&self,
|
||||
start_after: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedDepositsResponse, NyxdError> {
|
||||
self.query_ecash_contract(EcashQueryMsg::GetDepositsPaged { start_after, limit })
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait PagedEcashQueryClient: EcashQueryClient {
|
||||
async fn get_all_blacklisted_accounts(&self) -> Result<Vec<BlacklistedAccount>, NyxdError> {
|
||||
collect_paged!(self, get_blacklist_paged, accounts)
|
||||
}
|
||||
|
||||
async fn get_all_deposits(&self) -> Result<Vec<DepositData>, NyxdError> {
|
||||
collect_paged!(self, get_deposits_paged, deposits)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T> PagedEcashQueryClient for T where T: EcashQueryClient {}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<C> EcashQueryClient for C
|
||||
where
|
||||
C: CosmWasmClient + NymContractsProvider + Send + Sync,
|
||||
{
|
||||
async fn query_ecash_contract<T>(&self, query: EcashQueryMsg) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
let ecash_contract_address = self
|
||||
.ecash_contract_address()
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("coconut bandwidth contract"))?;
|
||||
self.query_contract_smart(ecash_contract_address, &query)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
||||
use nym_ecash_contract_common::msg::QueryMsg;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
fn all_query_variants_are_covered<C: EcashQueryClient + Send + Sync>(
|
||||
client: C,
|
||||
msg: EcashQueryMsg,
|
||||
) {
|
||||
match msg {
|
||||
EcashQueryMsg::GetBlacklistedAccount { public_key } => {
|
||||
client.get_blacklisted_account(public_key).ignore()
|
||||
}
|
||||
QueryMsg::GetBlacklistPaged { limit, start_after } => {
|
||||
client.get_blacklist_paged(start_after, limit).ignore()
|
||||
}
|
||||
QueryMsg::GetDeposit { deposit_id } => client.get_deposit(deposit_id).ignore(),
|
||||
QueryMsg::GetDepositsPaged { limit, start_after } => {
|
||||
client.get_deposits_paged(start_after, limit).ignore()
|
||||
}
|
||||
QueryMsg::GetRequiredDepositAmount {} => client.get_required_deposit_amount().ignore(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nyxd::contract_traits::NymContractsProvider;
|
||||
use crate::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{Coin, Fee, SigningCosmWasmClient};
|
||||
use crate::signing::signer::OfflineSigner;
|
||||
use async_trait::async_trait;
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg as EcashExecuteMsg;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait EcashSigningClient {
|
||||
async fn execute_ecash_contract(
|
||||
&self,
|
||||
fee: Option<Fee>,
|
||||
msg: EcashExecuteMsg,
|
||||
memo: String,
|
||||
funds: Vec<Coin>,
|
||||
) -> Result<ExecuteResult, NyxdError>;
|
||||
|
||||
async fn make_ticketbook_deposit(
|
||||
&self,
|
||||
public_key: String,
|
||||
deposit_amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = EcashExecuteMsg::DepositTicketBookFunds {
|
||||
identity_key: public_key,
|
||||
};
|
||||
self.execute_ecash_contract(fee, req, "Ecash::Deposit".to_string(), vec![deposit_amount])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn request_ticket_redemption(
|
||||
&self,
|
||||
commitment_bs58: String,
|
||||
number_of_tickets: u16,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = EcashExecuteMsg::RequestRedemption {
|
||||
commitment_bs58,
|
||||
number_of_tickets,
|
||||
};
|
||||
self.execute_ecash_contract(fee, req, Default::default(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_admin(
|
||||
&self,
|
||||
admin: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = EcashExecuteMsg::UpdateAdmin { admin };
|
||||
self.execute_ecash_contract(fee, req, "Ecash::UpdateAdmin".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_deposit_value(
|
||||
&self,
|
||||
new_deposit: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = EcashExecuteMsg::UpdateDepositValue {
|
||||
new_deposit: new_deposit.into(),
|
||||
};
|
||||
self.execute_ecash_contract(fee, req, "Ecash::UpdateDepositValue".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn propose_for_blacklist(
|
||||
&self,
|
||||
public_key: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = EcashExecuteMsg::ProposeToBlacklist { public_key };
|
||||
self.execute_ecash_contract(fee, req, "Ecash::ProposeToBlacklist".to_string(), vec![])
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl<C> EcashSigningClient for C
|
||||
where
|
||||
C: SigningCosmWasmClient + NymContractsProvider + Sync,
|
||||
NyxdError: From<<Self as OfflineSigner>::Error>,
|
||||
{
|
||||
async fn execute_ecash_contract(
|
||||
&self,
|
||||
fee: Option<Fee>,
|
||||
msg: EcashExecuteMsg,
|
||||
memo: String,
|
||||
funds: Vec<Coin>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let ecash_contract_address = self
|
||||
.ecash_contract_address()
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("coconut bandwidth contract"))?;
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
|
||||
self.execute(
|
||||
signer_address,
|
||||
ecash_contract_address,
|
||||
&msg,
|
||||
fee,
|
||||
memo,
|
||||
funds,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue};
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
fn all_execute_variants_are_covered<C: EcashSigningClient + Send + Sync>(
|
||||
client: C,
|
||||
msg: EcashExecuteMsg,
|
||||
) {
|
||||
match msg {
|
||||
EcashExecuteMsg::DepositTicketBookFunds { identity_key } => client
|
||||
.make_ticketbook_deposit(identity_key.to_string(), mock_coin(), None)
|
||||
.ignore(),
|
||||
EcashExecuteMsg::AddToBlacklist { public_key: _ } => unimplemented!(), //no add to blacklist method on client
|
||||
EcashExecuteMsg::ProposeToBlacklist { public_key } => {
|
||||
client.propose_for_blacklist(public_key, None).ignore()
|
||||
}
|
||||
ExecuteMsg::RequestRedemption {
|
||||
commitment_bs58,
|
||||
number_of_tickets,
|
||||
} => client
|
||||
.request_ticket_redemption(commitment_bs58, number_of_tickets, None)
|
||||
.ignore(),
|
||||
ExecuteMsg::RedeemTickets { .. } => unimplemented!(), // no redeem tickets method for the client
|
||||
ExecuteMsg::UpdateAdmin { admin } => client.update_admin(admin, None).ignore(),
|
||||
ExecuteMsg::UpdateDepositValue { new_deposit } => client
|
||||
.update_deposit_value(new_deposit.into(), None)
|
||||
.ignore(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -683,6 +683,24 @@ pub trait MixnetSigningClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn migrate_vested_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(fee, MixnetExecuteMsg::MigrateVestedMixNode {}, vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn migrate_vested_delegation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::MigrateVestedDelegation { mix_id },
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
async fn testing_resolve_all_pending_events(
|
||||
&self,
|
||||
@@ -928,6 +946,12 @@ mod tests {
|
||||
MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => client
|
||||
.withdraw_delegator_reward_on_behalf(owner.parse().unwrap(), mix_id, None)
|
||||
.ignore(),
|
||||
MixnetExecuteMsg::MigrateVestedMixNode { .. } => {
|
||||
client.migrate_vested_mixnode(None).ignore()
|
||||
}
|
||||
MixnetExecuteMsg::MigrateVestedDelegation { mix_id } => {
|
||||
client.migrate_vested_delegation(mix_id, None).ignore()
|
||||
}
|
||||
|
||||
#[cfg(feature = "contract-testing")]
|
||||
MixnetExecuteMsg::TestingResolveAllPendingEvents { .. } => {
|
||||
|
||||
@@ -8,34 +8,32 @@ use std::str::FromStr;
|
||||
// TODO: all of those could/should be derived via a macro
|
||||
|
||||
// query clients
|
||||
pub mod coconut_bandwidth_query_client;
|
||||
pub mod dkg_query_client;
|
||||
pub mod ecash_query_client;
|
||||
pub mod group_query_client;
|
||||
pub mod mixnet_query_client;
|
||||
pub mod multisig_query_client;
|
||||
pub mod vesting_query_client;
|
||||
|
||||
// signing clients
|
||||
pub mod coconut_bandwidth_signing_client;
|
||||
pub mod dkg_signing_client;
|
||||
pub mod ecash_signing_client;
|
||||
pub mod group_signing_client;
|
||||
pub mod mixnet_signing_client;
|
||||
pub mod multisig_signing_client;
|
||||
pub mod vesting_signing_client;
|
||||
|
||||
// re-export query traits
|
||||
pub use coconut_bandwidth_query_client::{
|
||||
CoconutBandwidthQueryClient, PagedCoconutBandwidthQueryClient,
|
||||
};
|
||||
pub use dkg_query_client::{DkgQueryClient, PagedDkgQueryClient};
|
||||
pub use ecash_query_client::{EcashQueryClient, PagedEcashQueryClient};
|
||||
pub use group_query_client::{GroupQueryClient, PagedGroupQueryClient};
|
||||
pub use mixnet_query_client::{MixnetQueryClient, PagedMixnetQueryClient};
|
||||
pub use multisig_query_client::{MultisigQueryClient, PagedMultisigQueryClient};
|
||||
pub use vesting_query_client::{PagedVestingQueryClient, VestingQueryClient};
|
||||
|
||||
// re-export signing traits
|
||||
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
|
||||
pub use dkg_signing_client::DkgSigningClient;
|
||||
pub use ecash_signing_client::EcashSigningClient;
|
||||
pub use group_signing_client::GroupSigningClient;
|
||||
pub use mixnet_signing_client::MixnetSigningClient;
|
||||
pub use multisig_signing_client::MultisigSigningClient;
|
||||
@@ -48,7 +46,7 @@ pub trait NymContractsProvider {
|
||||
fn vesting_contract_address(&self) -> Option<&AccountId>;
|
||||
|
||||
// coconut-related
|
||||
fn coconut_bandwidth_contract_address(&self) -> Option<&AccountId>;
|
||||
fn ecash_contract_address(&self) -> Option<&AccountId>;
|
||||
fn dkg_contract_address(&self) -> Option<&AccountId>;
|
||||
fn group_contract_address(&self) -> Option<&AccountId>;
|
||||
fn multisig_contract_address(&self) -> Option<&AccountId>;
|
||||
@@ -59,7 +57,7 @@ pub struct TypedNymContracts {
|
||||
pub mixnet_contract_address: Option<AccountId>,
|
||||
pub vesting_contract_address: Option<AccountId>,
|
||||
|
||||
pub coconut_bandwidth_contract_address: Option<AccountId>,
|
||||
pub ecash_contract_address: Option<AccountId>,
|
||||
pub group_contract_address: Option<AccountId>,
|
||||
pub multisig_contract_address: Option<AccountId>,
|
||||
pub coconut_dkg_contract_address: Option<AccountId>,
|
||||
@@ -78,8 +76,8 @@ impl TryFrom<NymContracts> for TypedNymContracts {
|
||||
.vesting_contract_address
|
||||
.map(|addr| addr.parse())
|
||||
.transpose()?,
|
||||
coconut_bandwidth_contract_address: value
|
||||
.coconut_bandwidth_contract_address
|
||||
ecash_contract_address: value
|
||||
.ecash_contract_address
|
||||
.map(|addr| addr.parse())
|
||||
.transpose()?,
|
||||
group_contract_address: value
|
||||
|
||||
+23
-1
@@ -6,7 +6,7 @@ use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::CosmWasmClient;
|
||||
use async_trait::async_trait;
|
||||
use cw3::{
|
||||
ProposalListResponse, ProposalResponse, VoteListResponse, VoteResponse, VoterDetail,
|
||||
ProposalListResponse, ProposalResponse, VoteInfo, VoteListResponse, VoteResponse, VoterDetail,
|
||||
VoterListResponse, VoterResponse,
|
||||
};
|
||||
use cw_utils::ThresholdResponse;
|
||||
@@ -134,6 +134,28 @@ pub trait PagedMultisigQueryClient: MultisigQueryClient {
|
||||
|
||||
Ok(voters)
|
||||
}
|
||||
|
||||
async fn get_all_votes(&self, proposal_id: u64) -> Result<Vec<VoteInfo>, NyxdError> {
|
||||
let mut votes = Vec::new();
|
||||
let mut start_after = None;
|
||||
|
||||
loop {
|
||||
let mut paged_response = self
|
||||
.list_votes(proposal_id, start_after.take(), None)
|
||||
.await?;
|
||||
|
||||
let last_voter = paged_response.votes.last().map(|vote| vote.voter.clone());
|
||||
votes.append(&mut paged_response.votes);
|
||||
|
||||
if let Some(start_after_res) = last_voter {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(votes)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
+3
-3
@@ -31,15 +31,15 @@ pub trait MultisigSigningClient: NymContractsProvider {
|
||||
voucher_value: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let coconut_bandwidth_contract_address = self
|
||||
.coconut_bandwidth_contract_address()
|
||||
let ecash_contract_address = self
|
||||
.ecash_contract_address()
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("coconut bandwidth contract"))?;
|
||||
|
||||
let release_funds_req = CoconutBandwidthExecuteMsg::ReleaseFunds {
|
||||
funds: voucher_value.into(),
|
||||
};
|
||||
let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: coconut_bandwidth_contract_address.to_string(),
|
||||
contract_addr: ecash_contract_address.to_string(),
|
||||
msg: to_binary(&release_funds_req)?,
|
||||
funds: vec![],
|
||||
});
|
||||
|
||||
@@ -437,6 +437,7 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue};
|
||||
use nym_vesting_contract_common::ExecuteMsg;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
@@ -560,6 +561,9 @@ mod tests {
|
||||
VestingExecuteMsg::UpdateLockedPledgeCap { address, cap } => client
|
||||
.update_locked_pledge_cap(address.parse().unwrap(), cap, None)
|
||||
.ignore(),
|
||||
// those will never be manually called by clients
|
||||
ExecuteMsg::TrackMigratedMixnode { .. } => "explicitly_ignored".ignore(),
|
||||
ExecuteMsg::TrackMigratedDelegation { .. } => "explicitly_ignored".ignore(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+22
-177
@@ -2,17 +2,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nyxd::cosmwasm_client::client_traits::CosmWasmClient;
|
||||
use crate::nyxd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse};
|
||||
use crate::nyxd::cosmwasm_client::logs::{self, parse_raw_logs};
|
||||
use crate::nyxd::cosmwasm_client::helpers::{
|
||||
compress_wasm_code, parse_msg_responses, CheckResponse,
|
||||
};
|
||||
use crate::nyxd::cosmwasm_client::logs::parse_raw_logs;
|
||||
use crate::nyxd::cosmwasm_client::types::*;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER};
|
||||
use crate::nyxd::helpers::find_tx_attribute;
|
||||
use crate::nyxd::{Coin, GasAdjustable, GasPrice, TxResponse};
|
||||
use crate::signing::signer::OfflineSigner;
|
||||
use crate::signing::tx_signer::TxSigner;
|
||||
use crate::signing::SignerData;
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::bank::MsgSend;
|
||||
use cosmrs::cosmwasm::{MsgClearAdmin, MsgUpdateAdmin};
|
||||
use cosmrs::distribution::MsgWithdrawDelegatorReward;
|
||||
use cosmrs::feegrant::{
|
||||
AllowedMsgAllowance, BasicAllowance, MsgGrantAllowance, MsgRevokeAllowance,
|
||||
@@ -25,7 +29,6 @@ use log::debug;
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use sha2::Sha256;
|
||||
|
||||
use std::time::SystemTime;
|
||||
use tendermint_rpc::endpoint::broadcast;
|
||||
|
||||
@@ -117,7 +120,7 @@ where
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let logs = parse_raw_logs(tx_res.tx_result.log)?;
|
||||
let logs = parse_raw_logs(&tx_res.tx_result.log)?;
|
||||
let gas_info = GasInfo {
|
||||
gas_wanted: tx_res.tx_result.gas_wanted.try_into().unwrap_or_default(),
|
||||
gas_used: tx_res.tx_result.gas_used.try_into().unwrap_or_default(),
|
||||
@@ -127,9 +130,8 @@ where
|
||||
// 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 = find_tx_attribute(&tx_res, "store_code", "code_id")
|
||||
.unwrap()
|
||||
.value
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
@@ -140,6 +142,7 @@ where
|
||||
compressed_checksum,
|
||||
code_id,
|
||||
logs,
|
||||
events: tx_res.tx_result.events,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -181,7 +184,7 @@ where
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let logs = parse_raw_logs(tx_res.tx_result.log)?;
|
||||
let logs = parse_raw_logs(&tx_res.tx_result.log)?;
|
||||
let gas_info = GasInfo {
|
||||
gas_wanted: tx_res.tx_result.gas_wanted.try_into().unwrap_or_default(),
|
||||
gas_used: tx_res.tx_result.gas_used.try_into().unwrap_or_default(),
|
||||
@@ -190,15 +193,15 @@ where
|
||||
// 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 = find_tx_attribute(&tx_res, "instantiate", "_contract_address")
|
||||
.unwrap()
|
||||
.value
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
Ok(InstantiateResult {
|
||||
contract_address,
|
||||
logs,
|
||||
events: tx_res.tx_result.events,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -212,7 +215,7 @@ where
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<ChangeAdminResult, NyxdError> {
|
||||
let change_admin_msg = sealed::cosmwasm::MsgUpdateAdmin {
|
||||
let change_admin_msg = MsgUpdateAdmin {
|
||||
sender: sender_address.clone(),
|
||||
new_admin: new_admin.clone(),
|
||||
contract: contract_address.clone(),
|
||||
@@ -231,6 +234,7 @@ where
|
||||
};
|
||||
Ok(ChangeAdminResult {
|
||||
logs: parse_raw_logs(tx_res.tx_result.log)?,
|
||||
events: tx_res.tx_result.events,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -243,7 +247,7 @@ where
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<ChangeAdminResult, NyxdError> {
|
||||
let change_admin_msg = sealed::cosmwasm::MsgClearAdmin {
|
||||
let change_admin_msg = MsgClearAdmin {
|
||||
sender: sender_address.clone(),
|
||||
contract: contract_address.clone(),
|
||||
}
|
||||
@@ -261,6 +265,7 @@ where
|
||||
};
|
||||
Ok(ChangeAdminResult {
|
||||
logs: parse_raw_logs(tx_res.tx_result.log)?,
|
||||
events: tx_res.tx_result.events,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -298,6 +303,7 @@ where
|
||||
};
|
||||
Ok(MigrateResult {
|
||||
logs: parse_raw_logs(tx_res.tx_result.log)?,
|
||||
events: tx_res.tx_result.events,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -333,9 +339,11 @@ where
|
||||
gas_wanted: tx_res.tx_result.gas_wanted.try_into().unwrap_or_default(),
|
||||
gas_used: tx_res.tx_result.gas_used.try_into().unwrap_or_default(),
|
||||
};
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: parse_raw_logs(tx_res.tx_result.log)?,
|
||||
data: tx_res.tx_result.data.into(),
|
||||
msg_responses: parse_msg_responses(tx_res.tx_result.data),
|
||||
events: tx_res.tx_result.events,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -378,7 +386,8 @@ where
|
||||
};
|
||||
Ok(ExecuteResult {
|
||||
logs: parse_raw_logs(tx_res.tx_result.log)?,
|
||||
data: tx_res.tx_result.data.into(),
|
||||
msg_responses: parse_msg_responses(tx_res.tx_result.data),
|
||||
events: tx_res.tx_result.events,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -707,167 +716,3 @@ where
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
// a temporary bypass until https://github.com/cosmos/cosmos-rust/pull/419 is merged
|
||||
mod sealed {
|
||||
pub mod cosmwasm {
|
||||
use cosmrs::{proto, tx::Msg, AccountId, ErrorReport, Result};
|
||||
|
||||
/// MsgUpdateAdmin sets a new admin for a smart contract
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct MsgUpdateAdmin {
|
||||
/// Sender is the that actor that signed the messages
|
||||
pub sender: AccountId,
|
||||
|
||||
/// NewAdmin address to be set
|
||||
pub new_admin: AccountId,
|
||||
|
||||
/// Contract is the address of the smart contract
|
||||
pub contract: AccountId,
|
||||
}
|
||||
|
||||
impl Msg for MsgUpdateAdmin {
|
||||
type Proto = proto::cosmwasm::wasm::v1::MsgUpdateAdmin;
|
||||
}
|
||||
|
||||
impl TryFrom<proto::cosmwasm::wasm::v1::MsgUpdateAdmin> for MsgUpdateAdmin {
|
||||
type Error = ErrorReport;
|
||||
|
||||
fn try_from(
|
||||
proto: proto::cosmwasm::wasm::v1::MsgUpdateAdmin,
|
||||
) -> Result<MsgUpdateAdmin> {
|
||||
MsgUpdateAdmin::try_from(&proto)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&proto::cosmwasm::wasm::v1::MsgUpdateAdmin> for MsgUpdateAdmin {
|
||||
type Error = ErrorReport;
|
||||
|
||||
fn try_from(
|
||||
proto: &proto::cosmwasm::wasm::v1::MsgUpdateAdmin,
|
||||
) -> Result<MsgUpdateAdmin> {
|
||||
Ok(MsgUpdateAdmin {
|
||||
sender: proto.sender.parse()?,
|
||||
new_admin: proto.new_admin.parse()?,
|
||||
contract: proto.contract.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MsgUpdateAdmin> for proto::cosmwasm::wasm::v1::MsgUpdateAdmin {
|
||||
fn from(msg: MsgUpdateAdmin) -> proto::cosmwasm::wasm::v1::MsgUpdateAdmin {
|
||||
proto::cosmwasm::wasm::v1::MsgUpdateAdmin::from(&msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MsgUpdateAdmin> for proto::cosmwasm::wasm::v1::MsgUpdateAdmin {
|
||||
fn from(msg: &MsgUpdateAdmin) -> proto::cosmwasm::wasm::v1::MsgUpdateAdmin {
|
||||
proto::cosmwasm::wasm::v1::MsgUpdateAdmin {
|
||||
sender: msg.sender.to_string(),
|
||||
new_admin: msg.new_admin.to_string(),
|
||||
contract: msg.contract.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// MsgUpdateAdminResponse returns empty data
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct MsgUpdateAdminResponse {}
|
||||
|
||||
impl Msg for MsgUpdateAdminResponse {
|
||||
type Proto = proto::cosmwasm::wasm::v1::MsgUpdateAdminResponse;
|
||||
}
|
||||
|
||||
impl TryFrom<proto::cosmwasm::wasm::v1::MsgUpdateAdminResponse> for MsgUpdateAdminResponse {
|
||||
type Error = ErrorReport;
|
||||
|
||||
fn try_from(
|
||||
_proto: proto::cosmwasm::wasm::v1::MsgUpdateAdminResponse,
|
||||
) -> Result<MsgUpdateAdminResponse> {
|
||||
Ok(MsgUpdateAdminResponse {})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MsgUpdateAdminResponse> for proto::cosmwasm::wasm::v1::MsgUpdateAdminResponse {
|
||||
fn from(
|
||||
_msg: MsgUpdateAdminResponse,
|
||||
) -> proto::cosmwasm::wasm::v1::MsgUpdateAdminResponse {
|
||||
proto::cosmwasm::wasm::v1::MsgUpdateAdminResponse {}
|
||||
}
|
||||
}
|
||||
|
||||
/// MsgClearAdmin removes any admin stored for a smart contract
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct MsgClearAdmin {
|
||||
/// Sender is the that actor that signed the messages
|
||||
pub sender: AccountId,
|
||||
|
||||
/// Contract is the address of the smart contract
|
||||
pub contract: AccountId,
|
||||
}
|
||||
|
||||
impl Msg for MsgClearAdmin {
|
||||
type Proto = proto::cosmwasm::wasm::v1::MsgClearAdmin;
|
||||
}
|
||||
|
||||
impl TryFrom<proto::cosmwasm::wasm::v1::MsgClearAdmin> for MsgClearAdmin {
|
||||
type Error = ErrorReport;
|
||||
|
||||
fn try_from(proto: proto::cosmwasm::wasm::v1::MsgClearAdmin) -> Result<MsgClearAdmin> {
|
||||
MsgClearAdmin::try_from(&proto)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&proto::cosmwasm::wasm::v1::MsgClearAdmin> for MsgClearAdmin {
|
||||
type Error = ErrorReport;
|
||||
|
||||
fn try_from(proto: &proto::cosmwasm::wasm::v1::MsgClearAdmin) -> Result<MsgClearAdmin> {
|
||||
Ok(MsgClearAdmin {
|
||||
sender: proto.sender.parse()?,
|
||||
contract: proto.contract.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MsgClearAdmin> for proto::cosmwasm::wasm::v1::MsgClearAdmin {
|
||||
fn from(msg: MsgClearAdmin) -> proto::cosmwasm::wasm::v1::MsgClearAdmin {
|
||||
proto::cosmwasm::wasm::v1::MsgClearAdmin::from(&msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MsgClearAdmin> for proto::cosmwasm::wasm::v1::MsgClearAdmin {
|
||||
fn from(msg: &MsgClearAdmin) -> proto::cosmwasm::wasm::v1::MsgClearAdmin {
|
||||
proto::cosmwasm::wasm::v1::MsgClearAdmin {
|
||||
sender: msg.sender.to_string(),
|
||||
contract: msg.contract.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// MsgClearAdminResponse returns empty data
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct MsgClearAdminResponse {}
|
||||
|
||||
impl Msg for MsgClearAdminResponse {
|
||||
type Proto = proto::cosmwasm::wasm::v1::MsgClearAdminResponse;
|
||||
}
|
||||
|
||||
impl TryFrom<proto::cosmwasm::wasm::v1::MsgClearAdminResponse> for MsgClearAdminResponse {
|
||||
type Error = ErrorReport;
|
||||
|
||||
fn try_from(
|
||||
_proto: proto::cosmwasm::wasm::v1::MsgClearAdminResponse,
|
||||
) -> Result<MsgClearAdminResponse> {
|
||||
Ok(MsgClearAdminResponse {})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MsgClearAdminResponse> for proto::cosmwasm::wasm::v1::MsgClearAdminResponse {
|
||||
fn from(
|
||||
_msg: MsgClearAdminResponse,
|
||||
) -> proto::cosmwasm::wasm::v1::MsgClearAdminResponse {
|
||||
proto::cosmwasm::wasm::v1::MsgClearAdminResponse {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,87 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use cosmrs::abci::TxMsgData;
|
||||
use cosmrs::cosmwasm::MsgExecuteContractResponse;
|
||||
use cosmrs::proto::cosmos::base::query::v1beta1::{PageRequest, PageResponse};
|
||||
use log::error;
|
||||
use prost::bytes::Bytes;
|
||||
use tendermint_rpc::endpoint::broadcast;
|
||||
|
||||
use crate::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
pub use cosmrs::abci::MsgResponse;
|
||||
|
||||
pub fn parse_msg_responses(data: Bytes) -> Vec<MsgResponse> {
|
||||
// it seems that currently, on wasmd 0.43 + tendermint-rs 0.37 + cosmrs 0.17.0-pre
|
||||
// the data is left in undecoded base64 form, but I'd imagine this might change so if the decoding fails,
|
||||
// use the bytes directly instead
|
||||
let data = if let Ok(decoded) = base64::decode(&data) {
|
||||
decoded
|
||||
} else {
|
||||
error!("failed to base64-decode the 'data' field of the TxResponse - has the chain been upgraded and introduced some breaking changes?");
|
||||
data.into()
|
||||
};
|
||||
|
||||
match TxMsgData::try_from(data) {
|
||||
Ok(tx_msg_data) => tx_msg_data.msg_responses,
|
||||
Err(err) => {
|
||||
error!("failed to parse tx responses - has the chain been upgraded and introduced some breaking changes? the error was {err}");
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// requires there's a single response message
|
||||
pub trait ToSingletonContractData: Sized {
|
||||
fn parse_singleton_u32_contract_data(&self) -> Result<u32, NyxdError> {
|
||||
let b = self.to_singleton_contract_data()?;
|
||||
if b.len() != 4 {
|
||||
return Err(NyxdError::MalformedResponseData {
|
||||
got: b.len(),
|
||||
expected: 4,
|
||||
});
|
||||
}
|
||||
Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
|
||||
}
|
||||
|
||||
fn parse_singleton_u64_contract_data(&self) -> Result<u64, NyxdError> {
|
||||
let b = self.to_singleton_contract_data()?;
|
||||
if b.len() != 8 {
|
||||
return Err(NyxdError::MalformedResponseData {
|
||||
got: b.len(),
|
||||
expected: 8,
|
||||
});
|
||||
}
|
||||
Ok(u64::from_be_bytes([
|
||||
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
]))
|
||||
}
|
||||
|
||||
fn to_singleton_contract_data(&self) -> Result<Vec<u8>, NyxdError>;
|
||||
}
|
||||
|
||||
impl ToSingletonContractData for ExecuteResult {
|
||||
fn to_singleton_contract_data(&self) -> Result<Vec<u8>, NyxdError> {
|
||||
if self.msg_responses.len() != 1 {
|
||||
return Err(NyxdError::UnexpectedNumberOfMsgResponses {
|
||||
got: self.msg_responses.len(),
|
||||
});
|
||||
}
|
||||
|
||||
self.msg_responses[0].to_contract_response_data()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToContractResponseData: Sized {
|
||||
fn to_contract_response_data(&self) -> Result<Vec<u8>, NyxdError>;
|
||||
}
|
||||
|
||||
impl ToContractResponseData for MsgResponse {
|
||||
fn to_contract_response_data(&self) -> Result<Vec<u8>, NyxdError> {
|
||||
Ok(self.try_decode_as::<MsgExecuteContractResponse>()?.data)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait CheckResponse: Sized {
|
||||
fn check_response(self) -> Result<Self, NyxdError>;
|
||||
}
|
||||
|
||||
@@ -3,22 +3,19 @@
|
||||
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use itertools::Itertools;
|
||||
use nym_ecash_contract_common::events::PROPOSAL_ID_ATTRIBUTE_NAME;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use nym_coconut_bandwidth_contract_common::event_attributes::*;
|
||||
pub use nym_coconut_dkg_common::event_attributes::*;
|
||||
pub use nym_ecash_contract_common::event_attributes::*;
|
||||
|
||||
// it seems that currently validators just emit stringified events (which are also returned as part of deliverTx response)
|
||||
// as theirs logs
|
||||
// as their logs
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Log {
|
||||
#[serde(default)]
|
||||
// weird thing is that the first msg_index seems to always be undefined on the raw logs
|
||||
pub msg_index: usize,
|
||||
// unless I'm missing something obvious, the "log" type in cosmjs is always an empty string
|
||||
// and launchpad cosmos validator was setting it to what essentially is just the raw version of what
|
||||
// we received (and we don't care about launchpad, we, as the time of writing this, work on the stargate)
|
||||
// log: String,
|
||||
pub events: Vec<cosmwasm_std::Event>,
|
||||
}
|
||||
|
||||
@@ -37,8 +34,32 @@ pub fn find_attribute<'a>(
|
||||
.find(|attr| attr.key == attribute_key)
|
||||
}
|
||||
|
||||
// those two functions were separated so that the internal logic could actually be tested
|
||||
/// Search for the proposal id in the given log. It'll be in the LAST wasm event, with attribute key "proposal_id"
|
||||
pub fn find_proposal_id(logs: &[Log]) -> Result<u64, NyxdError> {
|
||||
let maybe_attributes = logs
|
||||
.iter()
|
||||
.rev()
|
||||
.flat_map(|log| log.events.iter())
|
||||
.find(|event| event.ty == "wasm")
|
||||
.ok_or(NyxdError::ComswasmEventNotFound)?
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|attr| attr.key == PROPOSAL_ID_ATTRIBUTE_NAME);
|
||||
let attribute = maybe_attributes.ok_or(NyxdError::ComswasmAttributeNotFound)?;
|
||||
|
||||
attribute
|
||||
.value
|
||||
.parse::<u64>()
|
||||
.map_err(|_| NyxdError::DeserializationError("proposal_id".into()))
|
||||
}
|
||||
|
||||
// these two functions were separated so that the internal logic could actually be tested
|
||||
fn parse_raw_str_logs(raw: &str) -> Result<Vec<Log>, NyxdError> {
|
||||
// From Cosmos SDK > 0.50 onwards, log field is not populated
|
||||
if raw.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let logs: Vec<Log> = serde_json::from_str(raw).map_err(|_| NyxdError::MalformedLogString)?;
|
||||
if logs.len() != logs.iter().unique_by(|log| log.msg_index).count() {
|
||||
// this check is only here because I don't yet fully understand raw log string generation and
|
||||
@@ -48,7 +69,7 @@ fn parse_raw_str_logs(raw: &str) -> Result<Vec<Log>, NyxdError> {
|
||||
Ok(logs)
|
||||
}
|
||||
|
||||
pub fn parse_raw_logs(raw: String) -> Result<Vec<Log>, NyxdError> {
|
||||
pub fn parse_raw_logs<S: AsRef<str>>(raw: S) -> Result<Vec<Log>, NyxdError> {
|
||||
parse_raw_str_logs(raw.as_ref())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TEMPORARY WORKAROUND:
|
||||
// those features are expected as the below should only get activated whenever
|
||||
// the corresponding features in tendermint-rpc are enabled transitively
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
use crate::nyxd::cosmwasm_client::client_traits::SigningCosmWasmClient;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{Config, GasPrice, Hash, Height};
|
||||
@@ -18,6 +23,8 @@ use tendermint_rpc::endpoint::*;
|
||||
use tendermint_rpc::query::Query;
|
||||
use tendermint_rpc::{Error as TendermintRpcError, Order, Paging, SimpleRequest};
|
||||
|
||||
pub use helpers::{ToContractResponseData, ToSingletonContractData};
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::http_client;
|
||||
#[cfg(feature = "http-client")]
|
||||
|
||||
@@ -30,6 +30,7 @@ use prost::Message;
|
||||
use serde::Serialize;
|
||||
|
||||
pub use cosmrs::abci::GasInfo;
|
||||
pub use cosmrs::abci::MsgResponse;
|
||||
|
||||
pub type ContractCodeId = u64;
|
||||
|
||||
@@ -232,13 +233,15 @@ pub struct UploadResult {
|
||||
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
pub events: Vec<abci::Event>,
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: Hash,
|
||||
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InstantiateOptions {
|
||||
/// The funds that are transferred from the sender to the newly created contract.
|
||||
/// The funds are transferred as part of the message execution after the contract address is
|
||||
@@ -260,6 +263,11 @@ impl InstantiateOptions {
|
||||
admin,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_admin(mut self, admin: AccountId) -> Self {
|
||||
self.admin = Some(admin);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -269,6 +277,8 @@ pub struct InstantiateResult {
|
||||
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
pub events: Vec<abci::Event>,
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: Hash,
|
||||
|
||||
@@ -279,6 +289,8 @@ pub struct InstantiateResult {
|
||||
pub struct ChangeAdminResult {
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
pub events: Vec<abci::Event>,
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: Hash,
|
||||
|
||||
@@ -289,6 +301,8 @@ pub struct ChangeAdminResult {
|
||||
pub struct MigrateResult {
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
pub events: Vec<abci::Event>,
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: Hash,
|
||||
|
||||
@@ -299,7 +313,9 @@ pub struct MigrateResult {
|
||||
pub struct ExecuteResult {
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
pub data: Vec<u8>,
|
||||
pub msg_responses: Vec<MsgResponse>,
|
||||
|
||||
pub events: Vec<abci::Event>,
|
||||
|
||||
/// Transaction hash (might be used as transaction ID)
|
||||
pub transaction_hash: Hash,
|
||||
|
||||
@@ -32,6 +32,12 @@ pub enum NyxdError {
|
||||
#[error("There was an issue on the cosmrs side: {0}")]
|
||||
CosmrsErrorReport(#[from] cosmrs::ErrorReport),
|
||||
|
||||
#[error("cosmwasm event not found")]
|
||||
ComswasmEventNotFound,
|
||||
|
||||
#[error("cosmwasm attribute not found")]
|
||||
ComswasmAttributeNotFound,
|
||||
|
||||
#[error("Failed to derive account address")]
|
||||
AccountDerivationError,
|
||||
|
||||
@@ -142,6 +148,12 @@ pub enum NyxdError {
|
||||
|
||||
#[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")]
|
||||
UnexpectedBech32Prefix { got: String, expected: String },
|
||||
|
||||
#[error("the transaction returned unexpected, {got}, number of MsgResponse. Expected to receive a single one")]
|
||||
UnexpectedNumberOfMsgResponses { got: usize },
|
||||
|
||||
#[error("the response data has invalid size. got {got} bytes, but expected {expected} bytes instead")]
|
||||
MalformedResponseData { got: usize, expected: usize },
|
||||
}
|
||||
|
||||
// The purpose of parsing the abci query result is that we want to generate the `pretty_log` if
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
|
||||
use crate::nyxd::TxResponse;
|
||||
|
||||
// Searches in events for an event of the given event type which contains an
|
||||
// attribute for with the given key.
|
||||
pub fn find_tx_attribute(tx: &TxResponse, event_type: &str, attribute_key: &str) -> Option<String> {
|
||||
let event = tx.tx_result.events.iter().find(|e| e.kind == event_type)?;
|
||||
let attribute = event
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|attr| attr.key == attribute_key)?;
|
||||
Some(attribute.value.clone())
|
||||
let attribute = event.attributes.iter().find(|&attr| {
|
||||
if let Ok(key_str) = attr.key_str() {
|
||||
key_str == attribute_key
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})?;
|
||||
Some(attribute.value_str().ok().map(|str| str.to_string())).flatten()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TEMPORARY WORKAROUND:
|
||||
// those features are expected as the below should only get activated whenever
|
||||
// the corresponding features in tendermint-rpc are enabled transitively
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
use crate::nyxd::contract_traits::{NymContractsProvider, TypedNymContracts};
|
||||
use crate::nyxd::cosmwasm_client::types::{
|
||||
ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions, InstantiateResult,
|
||||
@@ -240,8 +245,8 @@ impl<C, S> NyxdClient<C, S> {
|
||||
self.config.contracts.vesting_contract_address = Some(address);
|
||||
}
|
||||
|
||||
pub fn set_coconut_bandwidth_contract_address(&mut self, address: AccountId) {
|
||||
self.config.contracts.coconut_bandwidth_contract_address = Some(address);
|
||||
pub fn set_ecash_contract_address(&mut self, address: AccountId) {
|
||||
self.config.contracts.ecash_contract_address = Some(address);
|
||||
}
|
||||
|
||||
pub fn set_multisig_contract_address(&mut self, address: AccountId) {
|
||||
@@ -262,11 +267,8 @@ impl<C, S> NymContractsProvider for NyxdClient<C, S> {
|
||||
self.config.contracts.vesting_contract_address.as_ref()
|
||||
}
|
||||
|
||||
fn coconut_bandwidth_contract_address(&self) -> Option<&AccountId> {
|
||||
self.config
|
||||
.contracts
|
||||
.coconut_bandwidth_contract_address
|
||||
.as_ref()
|
||||
fn ecash_contract_address(&self) -> Option<&AccountId> {
|
||||
self.config.contracts.ecash_contract_address.as_ref()
|
||||
}
|
||||
|
||||
fn dkg_contract_address(&self) -> Option<&AccountId> {
|
||||
@@ -379,6 +381,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mix_coin(&self, amount: u128) -> Coin {
|
||||
Coin::new(amount, &self.config.chain_details.mix_denom.base)
|
||||
}
|
||||
|
||||
pub fn mix_coins(&self, amount: u128) -> Vec<Coin> {
|
||||
vec![self.mix_coin(amount)]
|
||||
}
|
||||
|
||||
pub fn cw_address(&self) -> Addr {
|
||||
// the call to unchecked is fine here as we're converting directly from `AccountId`
|
||||
// which must have been a valid bech32 address
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TEMPORARY WORKAROUND:
|
||||
// those features are expected as the below should only get activated whenever
|
||||
// the corresponding features in tendermint-rpc are enabled transitively
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::tendermint::{self, abci, block::Height, evidence::Evidence, Genesis, Hash};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
@@ -43,9 +43,9 @@ nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-contract" }
|
||||
nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" }
|
||||
nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-sphinx = { path = "../../common/nymsphinx" }
|
||||
nym-client-core = { path = "../../common/client-core" }
|
||||
nym-config = { path = "../../common/config" }
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use anyhow::{anyhow, bail};
|
||||
use clap::ArgGroup;
|
||||
use clap::Parser;
|
||||
use futures::StreamExt;
|
||||
use log::{error, info};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_credential_utils::utils::block_until_coconut_is_available;
|
||||
use nym_credentials::coconut::bandwidth::freepass::MAX_FREE_PASS_VALIDITY;
|
||||
use nym_credentials::{
|
||||
obtain_aggregate_verification_key, IssuanceBandwidthCredential, IssuedBandwidthCredential,
|
||||
};
|
||||
use nym_credentials_interface::VerificationKey;
|
||||
use nym_validator_client::coconut::all_coconut_api_clients;
|
||||
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, NymContractsProvider};
|
||||
use nym_validator_client::nyxd::CosmWasmClient;
|
||||
use nym_validator_client::signing::AccountData;
|
||||
use nym_validator_client::CoconutApiClient;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
fn parse_rfc3339_expiration_date(raw: &str) -> Result<OffsetDateTime, time::error::Parse> {
|
||||
OffsetDateTime::parse(raw, &Rfc3339)
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(group(ArgGroup::new("expiration").required(true)))]
|
||||
pub struct Args {
|
||||
/// Specifies the expiration date of the free pass(es)
|
||||
/// Can't be set to more than a week into the future.
|
||||
#[clap(long, group = "expiration", value_parser = parse_rfc3339_expiration_date)]
|
||||
pub(crate) expiration_date: Option<OffsetDateTime>,
|
||||
|
||||
/// The expiration of the free pass(es) expresses as unix timestamp.
|
||||
/// Can't be set to more than a week into the future.
|
||||
#[clap(long, group = "expiration")]
|
||||
pub(crate) expiration_timestamp: Option<i64>,
|
||||
|
||||
/// The number of free passes to issue
|
||||
#[clap(long, default_value = "1")]
|
||||
pub(crate) amount: u64,
|
||||
|
||||
/// Path to the output directory for generated free passes.
|
||||
#[clap(long)]
|
||||
pub(crate) output_dir: PathBuf,
|
||||
}
|
||||
|
||||
async fn get_freepass(
|
||||
api_clients: Vec<CoconutApiClient>,
|
||||
aggregate_vk: &VerificationKey,
|
||||
threshold: u64,
|
||||
epoch_id: EpochId,
|
||||
signing_account: &AccountData,
|
||||
expiration_date: OffsetDateTime,
|
||||
) -> anyhow::Result<IssuedBandwidthCredential> {
|
||||
let issuance_pass = IssuanceBandwidthCredential::new_freepass(Some(expiration_date));
|
||||
let signing_data = issuance_pass.prepare_for_signing();
|
||||
|
||||
let credential_shares = Arc::new(tokio::sync::Mutex::new(Vec::new()));
|
||||
|
||||
futures::stream::iter(api_clients)
|
||||
.for_each_concurrent(None, |client| async {
|
||||
// move the client into the block
|
||||
let client = client;
|
||||
let api_url = client.api_client.api_url();
|
||||
|
||||
info!("contacting {api_url} for blinded free pass");
|
||||
|
||||
match issuance_pass
|
||||
.obtain_partial_freepass_credential(
|
||||
&client.api_client,
|
||||
signing_account,
|
||||
&client.verification_key,
|
||||
signing_data.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(partial_credential) => {
|
||||
credential_shares
|
||||
.lock()
|
||||
.await
|
||||
.push((partial_credential, client.node_id).into());
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to obtain partial free pass from {api_url}: {err}")
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
// SAFETY: the futures have completed, so we MUST have the only arc reference
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let credential_shares = Arc::into_inner(credential_shares).unwrap().into_inner();
|
||||
|
||||
if credential_shares.len() < threshold as usize {
|
||||
bail!("we managed to obtain only {} partial credentials while the minimum threshold is {threshold}", credential_shares.len());
|
||||
}
|
||||
|
||||
let signature = issuance_pass.aggregate_signature_shares(aggregate_vk, &credential_shares)?;
|
||||
Ok(issuance_pass.into_issued_credential(signature, epoch_id))
|
||||
}
|
||||
|
||||
pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
|
||||
let address = client.address();
|
||||
|
||||
if !args.output_dir.is_dir() {
|
||||
bail!("the provided output directory is not a directory!");
|
||||
}
|
||||
|
||||
if args.output_dir.read_dir()?.next().is_some() {
|
||||
bail!("the provided output directory is not empty!");
|
||||
}
|
||||
|
||||
let Some(bandwidth_contract) = client.coconut_bandwidth_contract_address() else {
|
||||
bail!("the bandwidth contract address is not set")
|
||||
};
|
||||
|
||||
let Some(bandwidth_admin) = client
|
||||
.get_contract(bandwidth_contract)
|
||||
.await
|
||||
.map(|c| c.contract_info.admin)?
|
||||
else {
|
||||
bail!("the bandwidth contract doesn't have any admin set")
|
||||
};
|
||||
|
||||
// sanity checks since nym-apis will reject invalid requests anyway
|
||||
if address != bandwidth_admin {
|
||||
bail!("the provided mnemonic does not correspond to the current admin of the bandwidth contract")
|
||||
}
|
||||
|
||||
let expiration_date = match args.expiration_date {
|
||||
Some(date) => date,
|
||||
// SAFETY: one of those arguments must have been set
|
||||
None => OffsetDateTime::from_unix_timestamp(args.expiration_timestamp.unwrap())?,
|
||||
};
|
||||
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
if expiration_date > now + MAX_FREE_PASS_VALIDITY {
|
||||
bail!("the provided free pass request has too long expiry (expiry is set to on {expiration_date})")
|
||||
}
|
||||
|
||||
if expiration_date < now {
|
||||
bail!("the provided free pass expiry is set in the past!")
|
||||
}
|
||||
|
||||
// issuance start
|
||||
block_until_coconut_is_available(&client).await?;
|
||||
|
||||
let signing_account = client.signing_account()?;
|
||||
|
||||
let epoch_id = client.get_current_epoch().await?.epoch_id;
|
||||
let threshold = client
|
||||
.get_current_epoch_threshold()
|
||||
.await?
|
||||
.ok_or(anyhow!("no threshold available"))?;
|
||||
let api_clients = all_coconut_api_clients(&client, epoch_id).await?;
|
||||
|
||||
if api_clients.len() < threshold as usize {
|
||||
bail!(
|
||||
"we have only {} api clients available while the minimum threshold is {threshold}",
|
||||
api_clients.len()
|
||||
)
|
||||
}
|
||||
let aggregate_vk = obtain_aggregate_verification_key(&api_clients)?;
|
||||
|
||||
for i in 0..args.amount {
|
||||
let human_index = i + 1;
|
||||
info!("trying to obtain free pass {human_index}/{}", args.amount);
|
||||
let free_pass = get_freepass(
|
||||
api_clients.clone(),
|
||||
&aggregate_vk,
|
||||
threshold,
|
||||
epoch_id,
|
||||
&signing_account,
|
||||
expiration_date,
|
||||
)
|
||||
.await?;
|
||||
let credential_data = Zeroizing::new(free_pass.pack_v1());
|
||||
let output = args.output_dir.join(format!("freepass_{i}.nym"));
|
||||
info!("saving the freepass to '{}'", output.display());
|
||||
File::create(output)?.write_all(&credential_data)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+18
-17
@@ -7,29 +7,22 @@ use anyhow::bail;
|
||||
use clap::Parser;
|
||||
use nym_credential_storage::initialise_persistent_storage;
|
||||
use nym_credential_utils::utils;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// Specify which type of ticketbook should be issued
|
||||
#[clap(long, default_value_t = TicketType::default())]
|
||||
pub(crate) ticketbook_type: TicketType,
|
||||
|
||||
/// Config file of the client that is supposed to use the credential.
|
||||
#[clap(long)]
|
||||
pub(crate) client_config: PathBuf,
|
||||
|
||||
/// The amount of utokens the credential will hold.
|
||||
#[clap(long, default_value = "0")]
|
||||
pub(crate) amount: u64,
|
||||
|
||||
/// Path to a directory used to store recovery files for unconsumed deposits
|
||||
#[clap(long)]
|
||||
pub(crate) recovery_dir: PathBuf,
|
||||
}
|
||||
|
||||
pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
|
||||
if args.amount == 0 {
|
||||
bail!("did not specify credential amount")
|
||||
}
|
||||
|
||||
let loaded = CommonConfigsWrapper::try_load(args.client_config)?;
|
||||
|
||||
if let Ok(id) = loaded.try_get_id() {
|
||||
@@ -40,16 +33,24 @@ pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
|
||||
bail!("the loaded config does not have a credentials store information")
|
||||
};
|
||||
|
||||
let Ok(private_id_key) = loaded.try_get_private_id_key() else {
|
||||
bail!("the loaded config does not have a public id key information")
|
||||
};
|
||||
|
||||
println!(
|
||||
"using credentials store at '{}'",
|
||||
credentials_store.display()
|
||||
);
|
||||
|
||||
let denom = &client.current_chain_details().mix_denom.base;
|
||||
let coin = Coin::new(args.amount as u128, denom);
|
||||
|
||||
let persistent_storage = initialise_persistent_storage(credentials_store).await;
|
||||
utils::issue_credential(&client, coin, &persistent_storage, args.recovery_dir).await?;
|
||||
let private_id_key: identity::PrivateKey = nym_pemstore::load_key(private_id_key)?;
|
||||
utils::issue_credential(
|
||||
&client,
|
||||
&persistent_storage,
|
||||
&private_id_key.to_bytes(),
|
||||
args.ticketbook_type,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3,22 +3,20 @@
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod generate_freepass;
|
||||
pub mod import_credential;
|
||||
pub mod issue_credentials;
|
||||
pub mod recover_credentials;
|
||||
pub mod import_ticket_book;
|
||||
pub mod issue_ticket_book;
|
||||
pub mod recover_ticket_book;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct Coconut {
|
||||
pub struct Ecash {
|
||||
#[clap(subcommand)]
|
||||
pub command: CoconutCommands,
|
||||
pub command: EcashCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum CoconutCommands {
|
||||
GenerateFreepass(generate_freepass::Args),
|
||||
IssueCredentials(issue_credentials::Args),
|
||||
RecoverCredentials(recover_credentials::Args),
|
||||
ImportCredential(import_credential::Args),
|
||||
pub enum EcashCommands {
|
||||
IssueTicketBook(issue_ticket_book::Args),
|
||||
RecoverTicketBook(recover_ticket_book::Args),
|
||||
ImportTicketBook(import_ticket_book::Args),
|
||||
}
|
||||
|
||||
+3
-10
@@ -6,7 +6,7 @@ use crate::utils::CommonConfigsWrapper;
|
||||
use anyhow::bail;
|
||||
use clap::Parser;
|
||||
use nym_credential_storage::initialise_persistent_storage;
|
||||
use nym_credential_utils::{recovery_storage, utils};
|
||||
use nym_credential_utils::utils;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -14,10 +14,6 @@ pub struct Args {
|
||||
/// Config file of the client that is supposed to use the credential.
|
||||
#[clap(long)]
|
||||
pub(crate) client_config: PathBuf,
|
||||
|
||||
/// Path to a directory used to store recovery files for unconsumed deposits
|
||||
#[clap(long)]
|
||||
pub(crate) recovery_dir: PathBuf,
|
||||
}
|
||||
|
||||
pub async fn execute(args: Args, client: QueryClient) -> anyhow::Result<()> {
|
||||
@@ -37,12 +33,9 @@ pub async fn execute(args: Args, client: QueryClient) -> anyhow::Result<()> {
|
||||
);
|
||||
|
||||
let persistent_storage = initialise_persistent_storage(credentials_store).await;
|
||||
let recovery_storage = recovery_storage::RecoveryStorage::new(args.recovery_dir)?;
|
||||
|
||||
let recovered =
|
||||
utils::recover_credentials(&client, &recovery_storage, &persistent_storage).await?;
|
||||
let recovered = utils::recover_deposits(&client, &persistent_storage).await?;
|
||||
|
||||
// TODO: denom?
|
||||
println!("recovered {recovered} worth of credentials");
|
||||
println!("recovered {recovered} ticketbooks");
|
||||
Ok(())
|
||||
}
|
||||
@@ -123,6 +123,21 @@ impl CommonConfigsWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_get_private_id_key(&self) -> anyhow::Result<PathBuf> {
|
||||
match self {
|
||||
CommonConfigsWrapper::NymClients(cfg) => Ok(cfg
|
||||
.storage_paths
|
||||
.inner
|
||||
.keys
|
||||
.private_identity_key_file
|
||||
.clone()),
|
||||
CommonConfigsWrapper::NymApi(_cfg) => {
|
||||
todo!() //SW this will depend on the new network monitor structure. Ping @Drazen
|
||||
}
|
||||
CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_private_id_key(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result<PathBuf> {
|
||||
match self {
|
||||
CommonConfigsWrapper::NymClients(cfg) => {
|
||||
@@ -225,4 +240,17 @@ impl UnknownConfigWrapper {
|
||||
bail!("no 'credentials_database_path' field present in the config")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_get_private_id_key(&self) -> anyhow::Result<PathBuf> {
|
||||
let id_val = self
|
||||
.find_value("keys.private_identity_key_file")
|
||||
.ok_or_else(|| {
|
||||
anyhow!("no 'keys.private_identity_key_file' field present in the config")
|
||||
})?;
|
||||
if let toml::Value::String(pub_id_key) = id_val {
|
||||
Ok(pub_id_key.parse()?)
|
||||
} else {
|
||||
bail!("no 'keys.private_identity_key_file' field present in the config")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-9
@@ -4,21 +4,25 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use cosmwasm_std::Coin;
|
||||
use log::{debug, info};
|
||||
|
||||
use nym_coconut_bandwidth_contract_common::msg::InstantiateMsg;
|
||||
use nym_ecash_contract_common::msg::InstantiateMsg;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub pool_addr: String,
|
||||
pub group_addr: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub multisig_addr: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub mix_denom: Option<String>,
|
||||
pub holding_account: AccountId,
|
||||
|
||||
#[clap(long, default_value = "75000000unym")]
|
||||
pub deposit_amount: Coin,
|
||||
}
|
||||
|
||||
pub async fn generate(args: Args) {
|
||||
@@ -26,21 +30,25 @@ pub async fn generate(args: Args) {
|
||||
|
||||
debug!("Received arguments: {:?}", args);
|
||||
|
||||
let multisig_addr = args.multisig_addr.unwrap_or_else(|| {
|
||||
let address = std::env::var(nym_network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
let group_addr = args.group_addr.unwrap_or_else(|| {
|
||||
let address = std::env::var(nym_network_defaults::var_names::GROUP_CONTRACT_ADDRESS)
|
||||
.expect("Multisig address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting multisig address to AccountId")
|
||||
});
|
||||
|
||||
let mix_denom = args.mix_denom.unwrap_or_else(|| {
|
||||
std::env::var(nym_network_defaults::var_names::MIX_DENOM).expect("Mix denom has to be set")
|
||||
let multisig_addr = args.multisig_addr.unwrap_or_else(|| {
|
||||
let address = std::env::var(nym_network_defaults::var_names::MULTISIG_CONTRACT_ADDRESS)
|
||||
.expect("Multisig address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting multisig address to AccountId")
|
||||
});
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
pool_addr: args.pool_addr,
|
||||
holding_account: args.holding_account.to_string(),
|
||||
group_addr: group_addr.to_string(),
|
||||
multisig_addr: multisig_addr.to_string(),
|
||||
mix_denom,
|
||||
deposit_amount: args.deposit_amount,
|
||||
};
|
||||
|
||||
debug!("instantiate_msg: {:?}", instantiate_msg);
|
||||
@@ -1,15 +1,26 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::{debug, info};
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
use nym_mixnet_contract_common::{InitialRewardingParams, InstantiateMsg, Percent};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use log::{debug, info};
|
||||
use nym_mixnet_contract_common::{
|
||||
InitialRewardingParams, InstantiateMsg, OperatingCostRange, Percent, ProfitMarginRange,
|
||||
};
|
||||
use nym_network_defaults::mainnet::MIX_DENOM;
|
||||
use nym_network_defaults::TOTAL_SUPPLY;
|
||||
use nym_validator_client::nyxd::{AccountId, Coin};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn default_maximum_operating_cost() -> Coin {
|
||||
Coin::new(TOTAL_SUPPLY, MIX_DENOM.base)
|
||||
}
|
||||
|
||||
pub fn default_minimum_operating_cost() -> Coin {
|
||||
Coin::new(0, MIX_DENOM.base)
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
@@ -50,6 +61,18 @@ pub struct Args {
|
||||
|
||||
#[clap(long, default_value_t = 240)]
|
||||
pub active_set_size: u32,
|
||||
|
||||
#[clap(long, default_value_t = Percent::zero())]
|
||||
pub minimum_profit_margin_percent: Percent,
|
||||
|
||||
#[clap(long, default_value_t = Percent::hundred())]
|
||||
pub maximum_profit_margin_percent: Percent,
|
||||
|
||||
#[clap(long, default_value_t = default_minimum_operating_cost())]
|
||||
pub minimum_interval_operating_cost: Coin,
|
||||
|
||||
#[clap(long, default_value_t = default_maximum_operating_cost())]
|
||||
pub maximum_interval_operating_cost: Coin,
|
||||
}
|
||||
|
||||
pub async fn generate(args: Args) {
|
||||
@@ -97,6 +120,10 @@ pub async fn generate(args: Args) {
|
||||
.expect("Rewarding (mix) denom has to be set")
|
||||
});
|
||||
|
||||
if args.minimum_interval_operating_cost.denom != args.maximum_interval_operating_cost.denom {
|
||||
panic!("different denoms for operating cost bounds")
|
||||
}
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
rewarding_validator_address: rewarding_validator_address.to_string(),
|
||||
vesting_contract_address: vesting_contract_address.to_string(),
|
||||
@@ -104,6 +131,14 @@ pub async fn generate(args: Args) {
|
||||
epochs_in_interval: args.epochs_in_interval,
|
||||
epoch_duration: Duration::from_secs(args.epoch_duration),
|
||||
initial_rewarding_params,
|
||||
profit_margin: ProfitMarginRange {
|
||||
minimum: args.minimum_profit_margin_percent,
|
||||
maximum: args.maximum_profit_margin_percent,
|
||||
},
|
||||
interval_operating_cost: OperatingCostRange {
|
||||
minimum: args.minimum_interval_operating_cost.amount.into(),
|
||||
maximum: args.maximum_interval_operating_cost.amount.into(),
|
||||
},
|
||||
};
|
||||
|
||||
debug!("instantiate_msg: {:?}", instantiate_msg);
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod coconut_bandwidth;
|
||||
pub mod coconut_dkg;
|
||||
pub mod ecash_bandwidth;
|
||||
pub mod mixnet;
|
||||
pub mod multisig;
|
||||
pub mod vesting;
|
||||
@@ -18,7 +18,7 @@ pub struct GenerateMessage {
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum GenerateMessageCommands {
|
||||
CoconutBandwidth(coconut_bandwidth::Args),
|
||||
EcashBandwidth(ecash_bandwidth::Args),
|
||||
CoconutDKG(coconut_dkg::Args),
|
||||
Mixnet(mixnet::Args),
|
||||
Multisig(multisig::Args),
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct Args {
|
||||
pub max_voting_period: u64,
|
||||
|
||||
#[clap(long)]
|
||||
pub coconut_bandwidth_contract_address: Option<AccountId>,
|
||||
pub ecash_contract_address: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub coconut_dkg_contract_address: Option<AccountId>,
|
||||
@@ -33,14 +33,12 @@ pub async fn generate(args: Args) {
|
||||
|
||||
debug!("Received arguments: {:?}", args);
|
||||
|
||||
let coconut_bandwidth_contract_address =
|
||||
args.coconut_bandwidth_contract_address.unwrap_or_else(|| {
|
||||
let address =
|
||||
std::env::var(nym_network_defaults::var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS)
|
||||
.expect("Coconut bandwidth contract address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting bandwidth contract address to AccountId")
|
||||
});
|
||||
let ecash_contract_address = args.ecash_contract_address.unwrap_or_else(|| {
|
||||
let address = std::env::var(nym_network_defaults::var_names::ECASH_CONTRACT_ADDRESS)
|
||||
.expect("Coconut bandwidth contract address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting bandwidth contract address to AccountId")
|
||||
});
|
||||
|
||||
let coconut_dkg_contract_address = args.coconut_dkg_contract_address.unwrap_or_else(|| {
|
||||
let address = std::env::var(nym_network_defaults::var_names::COCONUT_DKG_CONTRACT_ADDRESS)
|
||||
@@ -58,7 +56,7 @@ pub async fn generate(args: Args) {
|
||||
max_voting_period: Duration::Time(args.max_voting_period),
|
||||
executor: None,
|
||||
proposal_deposit: None,
|
||||
coconut_bandwidth_contract_address: coconut_bandwidth_contract_address.to_string(),
|
||||
coconut_bandwidth_contract_address: ecash_contract_address.to_string(),
|
||||
coconut_dkg_contract_address: coconut_dkg_contract_address.to_string(),
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub mix_id: Option<MixId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn migrate_vested_delegation(args: Args, client: SigningClient) {
|
||||
let mix_id = match args.mix_id {
|
||||
Some(mix_id) => mix_id,
|
||||
None => {
|
||||
let identity_key = args
|
||||
.identity_key
|
||||
.expect("either mix_id or mix_identity has to be specified");
|
||||
let node_details = client
|
||||
.get_mixnode_details_by_identity(identity_key)
|
||||
.await
|
||||
.expect("contract query failed")
|
||||
.mixnode_details
|
||||
.expect("mixnode with the specified identity doesnt exist");
|
||||
node_details.mix_id()
|
||||
}
|
||||
};
|
||||
|
||||
let res = client
|
||||
.migrate_vested_delegation(mix_id, None)
|
||||
.await
|
||||
.expect("failed to migrate delegation!");
|
||||
|
||||
info!("migration result: {:?}", res)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ pub mod rewards;
|
||||
|
||||
pub mod delegate_to_mixnode;
|
||||
pub mod delegate_to_multiple_mixnodes;
|
||||
pub mod migrate_vested_delegation;
|
||||
pub mod query_for_delegations;
|
||||
pub mod undelegate_from_mixnode;
|
||||
pub mod vesting_delegate_to_mixnode;
|
||||
@@ -35,4 +36,6 @@ pub enum MixnetDelegatorsCommands {
|
||||
DelegateVesting(vesting_delegate_to_mixnode::Args),
|
||||
/// Undelegate from a mixnode (when originally using locked tokens)
|
||||
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
|
||||
/// Migrate the delegation to use liquid tokens
|
||||
MigrateVestedDelegation(migrate_vested_delegation::Args),
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user