Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6946151b25 | |||
| a4aee465fa | |||
| 0d71ac5e75 | |||
| ce14e40968 | |||
| d108edb424 | |||
| 18f5623b05 | |||
| 8e92801929 | |||
| 3fc1bc4e7c | |||
| 72de726762 | |||
| cb9dfa8188 | |||
| f102ed53a7 | |||
| a803c7f25e | |||
| f20b620cbb | |||
| d771d15959 | |||
| 49e6f387ff | |||
| 9568c0ba1d | |||
| 0b7b705e56 | |||
| 5daea675e7 | |||
| ebd18586a8 | |||
| 585610295f | |||
| 91653d13c6 | |||
| b84486c0f4 | |||
| b6a765481a | |||
| 8f52f34bc4 | |||
| 39798de1e8 | |||
| c650587e4c | |||
| 660d5d8b05 | |||
| 79f9db91ae | |||
| 43822f27a8 | |||
| e500d154dd | |||
| 3ceb00fae1 | |||
| d019343fd9 | |||
| f55a55b784 | |||
| 0ea8da79c8 | |||
| 0e12251773 | |||
| f886326014 | |||
| c73c2beb33 | |||
| b7aa84cd5a | |||
| b6b40163c6 | |||
| f46c0142e7 | |||
| 8774b22d84 | |||
| c74a880838 | |||
| ccbb254b1a | |||
| 2bd0cfc870 | |||
| aeaf31ed59 | |||
| 05820cfca7 | |||
| 0469d5b602 | |||
| d912844543 | |||
| 64757ebc83 | |||
| ba55affe0a | |||
| e9f826e705 | |||
| bfbd509e4b | |||
| b68fb4f5dd | |||
| 7461fe88d0 | |||
| 510d0333a1 | |||
| cea4887080 | |||
| 8f1cb67bf7 | |||
| 09b9601c7e | |||
| 2a1dd138e0 | |||
| 89925e49e8 | |||
| 96fc7208a2 | |||
| 9874daa061 | |||
| baa61c07d5 | |||
| 62e9c8236a | |||
| cf268ffcd5 | |||
| bdcfe42a1e | |||
| 51e30b2a89 | |||
| 76d4d0e7cb | |||
| 9df432b8a2 | |||
| 4021059e76 | |||
| 43ef098aad | |||
| bf1d2a12bc | |||
| c3f214ffad | |||
| 324fb6afe7 | |||
| ace020b5cf | |||
| 4b8fa4805e | |||
| eb18b49f3e | |||
| 2dc45fda1e | |||
| c2a113f1b3 | |||
| f805eebce7 | |||
| ad81160760 | |||
| 0931236a98 | |||
| b28ff17c30 | |||
| 6eb482fc4b | |||
| f9be735d4f | |||
| 0fd178a304 | |||
| 16ccbd9e48 | |||
| bd0ea45f35 | |||
| 28cc772d7b |
@@ -16,7 +16,10 @@ jobs:
|
||||
- name: Install cargo deny
|
||||
run: cargo install --locked cargo-deny
|
||||
- name: Run cargo deny
|
||||
run: cargo deny check advisories --hide-inclusion-graph &> .github/workflows/support-files/notifications/deny.message
|
||||
run: |
|
||||
find . -name Cargo.toml -exec cargo deny --manifest-path {} check \
|
||||
advisories -A advisory-not-detected --hide-inclusion-graph \; &> \
|
||||
>(uniq &> .github/workflows/support-files/notifications/deny.message )
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: report
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
name: Continuous integration on dispatch
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [ self-hosted, custom-linux ]
|
||||
# Enable sccache via environment variable
|
||||
env:
|
||||
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --all-features
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
name: Clippy checks
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features
|
||||
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace -- -D warnings
|
||||
|
||||
- name: Build all binaries with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --features=coconut -- -D warnings
|
||||
@@ -0,0 +1,56 @@
|
||||
name: CI for Network Explorer API
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
env:
|
||||
NETWORK: mainnet
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
|
||||
- name: Check the release tag starts with `nym-explorer-api-`
|
||||
if: startsWith(github.ref, 'refs/tags/nym-explorer-api-') == false && github.event_name != 'workflow_dispatch'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Release tag did not start with nym-explorer-api-...')
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Build all explorer-api
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path explorer-api/Cargo.toml --workspace --release
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: my-artifact
|
||||
path: |
|
||||
target/release/explorer-api
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: github.event_name == 'release'
|
||||
with:
|
||||
files: |
|
||||
target/release/explorer-api
|
||||
+38
-12
@@ -1,4 +1,4 @@
|
||||
name: Nightly builds on dispatch
|
||||
name: Nightly builds on latest release
|
||||
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
@@ -7,26 +7,40 @@ jobs:
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
# creates the matrix strategy from nightly_build_matrix_includes.json
|
||||
- uses: actions/checkout@v2
|
||||
# creates the matrix strategy from nightly_build_release_matrix.json
|
||||
- uses: actions/checkout@v3
|
||||
- id: set-matrix
|
||||
uses: JoshuaTheMiller/conditional-build-matrix@main
|
||||
with:
|
||||
inputFile: '.github/workflows/nightly_build_matrix_on_dispatch.json'
|
||||
inputFile: '.github/workflows/nightly_build_release_matrix.json'
|
||||
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
|
||||
build:
|
||||
get_release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: matrix_prep
|
||||
outputs:
|
||||
output1: ${{ steps.step2.outputs.lastest_release }}
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set output variable to latest release branch
|
||||
id: step2
|
||||
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+' | tail -n 1 | sed 's/origin\///')" >> $GITHUB_OUTPUT
|
||||
build:
|
||||
needs: [get_release,matrix_prep]
|
||||
strategy:
|
||||
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
- name: Check out latest release branch
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{needs.get_release.outputs.output1}}
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -42,6 +56,12 @@ jobs:
|
||||
command: build
|
||||
args: --workspace
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@@ -99,6 +119,12 @@ jobs:
|
||||
command: build
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@@ -151,7 +177,7 @@ jobs:
|
||||
- name: Collect jobs status
|
||||
uses: technote-space/workflow-conclusion-action@v2
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Keybase - Node Install
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
run: npm install
|
||||
@@ -160,14 +186,14 @@ jobs:
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: nightly
|
||||
NYM_PROJECT_NAME: "Nym nightly build"
|
||||
NYM_PROJECT_NAME: "Nym nightly build on latest release"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMTECH_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "${{ secrets.KEYBASE_CHANNEL_DEV_CORE_ID }}"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "ci-nightly-release"
|
||||
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
@@ -25,6 +25,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
|
||||
- name: Check the release tag starts with `nym-binaries-`
|
||||
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch'
|
||||
uses: actions/github-script@v3
|
||||
|
||||
+40
-27
@@ -6,35 +6,49 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
### Added
|
||||
|
||||
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577])
|
||||
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
|
||||
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611))
|
||||
- common/ledger: new library for communicating with a Ledger device ([#1640])
|
||||
- native-client/socks5-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
|
||||
- native-client/socks5-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
|
||||
- native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
|
||||
- wasm-client: uses updated wasm-compatible `client-core` so that it's now capable of packet retransmission, cover traffic and poisson delay (among other things!) ([#1673])
|
||||
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to cmpute reward estimation endpoint
|
||||
- native-client/socks5-client/network-requester: improve handling error cases ([#1713])
|
||||
- vesting-contract: optional locked token pledge cap per account ([#1687]), defaults to 100_000 NYM
|
||||
- clients: add testing-only support for two more extended packet sizes (8kb and 16kb).
|
||||
|
||||
### Fixed
|
||||
|
||||
- validator-api, mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
|
||||
- validator-api should now correctly update historical uptimes for all mixnodes and gateways every 24h ([#1721])
|
||||
- socks5-client: fix bug where in some cases packet reordering could trigger a connection being closed too early ([#1702],[#1724])
|
||||
- binaries: add `-c` shortform for `--config-env-file`
|
||||
|
||||
### Changed
|
||||
|
||||
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
|
||||
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
|
||||
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
|
||||
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
|
||||
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
|
||||
- moved `Percent` struct to to `contracts-common`, change affects explorer-api
|
||||
- clients: bound the sphinx packet channel and reduce sending rate if gateway can't keep up ([#1703],[#1725])
|
||||
- clients: add concept of transmission lanes to better handle multiple data streams ([#1720])
|
||||
|
||||
[#1720]: https://github.com/nymtech/nym/pull/1720
|
||||
|
||||
|
||||
## [v1.1.0](https://github.com/nymtech/nym/tree/v1.1.0) (2022-11-09)
|
||||
|
||||
### Added
|
||||
|
||||
- clients: add testing-only support for two more extended packet sizes (8kb and 16kb).
|
||||
- common/ledger: new library for communicating with a Ledger device ([#1640])
|
||||
- native-client/socks5-client/wasm-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
|
||||
- native-client/socks5-client/wasm-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
|
||||
- native-client/socks5-client/wasm-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
|
||||
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611])
|
||||
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to compute reward estimation endpoint
|
||||
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
|
||||
- wasm-client: uses updated wasm-compatible `client-core` so that it's now capable of packet retransmission, cover traffic and poisson delay (among other things!) ([#1673])
|
||||
|
||||
### Fixed
|
||||
|
||||
- socks5-client: fix bug where in some cases packet reordering could trigger a connection being closed too early ([#1702],[#1724])
|
||||
- validator-api: mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
|
||||
- validator-api: should now correctly update historical uptimes for all mixnodes and gateways every 24h ([#1721])
|
||||
|
||||
### Changed
|
||||
|
||||
- clients: bound the sphinx packet channel and reduce sending rate if gateway can't keep up ([#1703],[#1725])
|
||||
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
|
||||
- moved `Percent` struct to `contracts-common`, change affects explorer-api
|
||||
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
|
||||
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
|
||||
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
|
||||
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
|
||||
- validator-api: changes to internal SQL schema due to the mixnet contract revamp ([#1472])
|
||||
- validator-api: changes to internal data structures due to the mixnet contract revamp ([#1472])
|
||||
- validator-api: split epoch-operations into multiple separate transactions ([#1472])
|
||||
|
||||
[#1472]: https://github.com/nymtech/nym/pull/1472
|
||||
[#1541]: https://github.com/nymtech/nym/pull/1541
|
||||
[#1558]: https://github.com/nymtech/nym/pull/1558
|
||||
[#1577]: https://github.com/nymtech/nym/pull/1577
|
||||
@@ -42,16 +56,15 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#1591]: https://github.com/nymtech/nym/pull/1591
|
||||
[#1640]: https://github.com/nymtech/nym/pull/1640
|
||||
[#1645]: https://github.com/nymtech/nym/pull/1645
|
||||
[#1611]: https://github.com/nymtech/nym/pull/1611
|
||||
[#1664]: https://github.com/nymtech/nym/pull/1664
|
||||
[#1666]: https://github.com/nymtech/nym/pull/1645
|
||||
[#1669]: https://github.com/nymtech/nym/pull/1669
|
||||
[#1671]: https://github.com/nymtech/nym/pull/1671
|
||||
[#1673]: https://github.com/nymtech/nym/pull/1673
|
||||
[#1681]: https://github.com/nymtech/nym/pull/1681
|
||||
[#1687]: https://github.com/nymtech/nym/pull/1687
|
||||
[#1702]: https://github.com/nymtech/nym/pull/1702
|
||||
[#1703]: https://github.com/nymtech/nym/pull/1703
|
||||
[#1713]: https://github.com/nymtech/nym/pull/1713
|
||||
[#1721]: https://github.com/nymtech/nym/pull/1721
|
||||
[#1724]: https://github.com/nymtech/nym/pull/1724
|
||||
[#1725]: https://github.com/nymtech/nym/pull/1725
|
||||
|
||||
Generated
+24
-13
@@ -577,9 +577,17 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.0.1"
|
||||
name = "client-connections"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"client-connections",
|
||||
"config",
|
||||
"crypto",
|
||||
"dirs",
|
||||
@@ -850,9 +858,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.2"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -1582,7 +1590,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 3.2.8",
|
||||
@@ -3070,7 +3078,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@@ -3122,9 +3130,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"clap 3.2.8",
|
||||
"client-connections",
|
||||
"client-core",
|
||||
"coconut-interface",
|
||||
"completions",
|
||||
@@ -3160,7 +3169,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3207,7 +3216,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
@@ -3249,10 +3258,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"clap 3.2.8",
|
||||
"client-connections",
|
||||
"completions",
|
||||
"dirs",
|
||||
"futures",
|
||||
@@ -3296,9 +3306,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"clap 3.2.8",
|
||||
"client-connections",
|
||||
"client-core",
|
||||
"coconut-interface",
|
||||
"completions",
|
||||
@@ -3361,7 +3372,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator-api"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4123,6 +4134,7 @@ name = "proxy-helpers"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"client-connections",
|
||||
"futures",
|
||||
"log",
|
||||
"ordered-buffer",
|
||||
@@ -6516,7 +6528,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
||||
@@ -26,6 +26,7 @@ members = [
|
||||
"common/client-libs/gateway-client",
|
||||
"common/client-libs/mixnet-client",
|
||||
"common/client-libs/validator-client",
|
||||
"common/client-connections",
|
||||
"common/coconut-interface",
|
||||
"common/commands",
|
||||
"common/config",
|
||||
|
||||
@@ -92,6 +92,9 @@ build-wallet:
|
||||
build-connect:
|
||||
cargo build --manifest-path nym-connect/Cargo.toml --workspace
|
||||
|
||||
build-explorer-api:
|
||||
cargo build --manifest-path explorer-api/Cargo.toml --workspace
|
||||
|
||||
build-wasm-client:
|
||||
cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -14,11 +14,13 @@ log = "0.4"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sled = { version = "0.34", optional = true }
|
||||
tap = "1.0.1"
|
||||
thiserror = "1.0.34"
|
||||
url = { version ="2.2", features = ["serde"] }
|
||||
|
||||
# internal
|
||||
config = { path = "../../common/config" }
|
||||
client-connections = { path = "../../common/client-connections" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
#gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
|
||||
@@ -28,7 +30,6 @@ nymsphinx = { path = "../../common/nymsphinx" }
|
||||
pemstore = { path = "../../common/pemstore" }
|
||||
topology = { path = "../../common/topology" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
|
||||
tap = "1.0.1"
|
||||
|
||||
tokio = { version = "1.21.2", features = ["time", "macros"]}
|
||||
|
||||
@@ -56,4 +57,5 @@ tempfile = "3.1.0"
|
||||
default = ["reply-surb"]
|
||||
wasm = ["gateway-client/wasm"]
|
||||
coconut = ["gateway-client/coconut", "gateway-requests/coconut"]
|
||||
reply-surb = ["sled"]
|
||||
reply-surb = ["sled"]
|
||||
|
||||
|
||||
@@ -178,6 +178,10 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
// This isn't a problem, if the channel is full means we're already sending the
|
||||
// max amount of messages downstream can handle.
|
||||
log::debug!("Failed to send cover message - channel full");
|
||||
// However it's still useful to alert the user that the gateway or the link to
|
||||
// the gateway can't keep up. Either due to insufficient bandwidth on the
|
||||
// client side, or that the gateway is overloaded.
|
||||
log::warn!("Failed to send: gateway appears to not keep up");
|
||||
}
|
||||
TrySendError::Closed(_) => {
|
||||
log::warn!("Failed to send cover message - channel closed");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use client_connections::TransmissionLane;
|
||||
use futures::channel::mpsc;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
@@ -11,6 +12,7 @@ pub enum InputMessage {
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
lane: TransmissionLane,
|
||||
},
|
||||
Reply {
|
||||
reply_surb: ReplySurb,
|
||||
@@ -19,11 +21,17 @@ pub enum InputMessage {
|
||||
}
|
||||
|
||||
impl InputMessage {
|
||||
pub fn new_fresh(recipient: Recipient, data: Vec<u8>, with_reply_surb: bool) -> Self {
|
||||
pub fn new_fresh(
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
lane: TransmissionLane,
|
||||
) -> Self {
|
||||
InputMessage::Fresh {
|
||||
recipient,
|
||||
data,
|
||||
with_reply_surb,
|
||||
lane,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ impl AcknowledgementListener {
|
||||
}
|
||||
|
||||
async fn on_ack(&mut self, ack_content: Vec<u8>) {
|
||||
debug!("Received an ack");
|
||||
trace!("Received an ack");
|
||||
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
|
||||
.map(FragmentIdentifier::try_from_bytes)
|
||||
{
|
||||
|
||||
+14
-8
@@ -8,6 +8,7 @@ use crate::client::{
|
||||
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use client_connections::TransmissionLane;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
@@ -104,6 +105,7 @@ where
|
||||
content: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
) -> Option<Vec<RealMessage>> {
|
||||
log::trace!("handling msg size: {}", content.len());
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = match topology_permit
|
||||
.try_get_valid_topology_ref(&self.ack_recipient, Some(&recipient))
|
||||
@@ -164,26 +166,30 @@ where
|
||||
}
|
||||
|
||||
async fn on_input_message(&mut self, msg: InputMessage) {
|
||||
let real_messages = match msg {
|
||||
let (real_messages, lane) = match msg {
|
||||
InputMessage::Fresh {
|
||||
recipient,
|
||||
data,
|
||||
with_reply_surb,
|
||||
} => {
|
||||
lane,
|
||||
} => (
|
||||
self.handle_fresh_message(recipient, data, with_reply_surb)
|
||||
.await,
|
||||
lane,
|
||||
),
|
||||
InputMessage::Reply { reply_surb, data } => (
|
||||
self.handle_reply(reply_surb, data)
|
||||
.await
|
||||
}
|
||||
InputMessage::Reply { reply_surb, data } => self
|
||||
.handle_reply(reply_surb, data)
|
||||
.await
|
||||
.map(|message| vec![message]),
|
||||
.map(|message| vec![message]),
|
||||
TransmissionLane::Reply,
|
||||
),
|
||||
};
|
||||
|
||||
// there's no point in trying to send nothing
|
||||
if let Some(real_messages) = real_messages {
|
||||
// tells real message sender (with the poisson timer) to send this to the mix network
|
||||
self.real_message_sender
|
||||
.unbounded_send(real_messages)
|
||||
.unbounded_send((real_messages, lane))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
+13
-9
@@ -1,17 +1,21 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::action_controller::{Action, ActionSender};
|
||||
use super::PendingAcknowledgement;
|
||||
use super::RetransmissionRequestReceiver;
|
||||
use super::{
|
||||
action_controller::{Action, ActionSender},
|
||||
PendingAcknowledgement, RetransmissionRequestReceiver,
|
||||
};
|
||||
use crate::client::{
|
||||
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
|
||||
use client_connections::TransmissionLane;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::preparer::MessagePreparer;
|
||||
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
|
||||
use nymsphinx::{
|
||||
acknowledgements::AckKey, addressing::clients::Recipient, preparer::MessagePreparer,
|
||||
};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
@@ -113,10 +117,10 @@ where
|
||||
|
||||
// send to `OutQueueControl` to eventually send to the mix network
|
||||
self.real_message_sender
|
||||
.unbounded_send(vec![RealMessage::new(
|
||||
prepared_fragment.mix_packet,
|
||||
frag_id,
|
||||
)])
|
||||
.unbounded_send((
|
||||
vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)],
|
||||
TransmissionLane::Retransmission,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::client::{
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use crate::spawn_future;
|
||||
use client_connections::ClosedConnectionReceiver;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::AcknowledgementReceiver;
|
||||
use log::*;
|
||||
@@ -110,6 +111,7 @@ impl RealMessagesController<OsRng> {
|
||||
mix_sender: BatchMixMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
closed_connection_rx: ClosedConnectionReceiver,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
@@ -159,6 +161,7 @@ impl RealMessagesController<OsRng> {
|
||||
rng,
|
||||
config.self_recipient,
|
||||
topology_access,
|
||||
closed_connection_rx,
|
||||
);
|
||||
|
||||
RealMessagesController {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::client::mix_traffic::BatchMixMessageSender;
|
||||
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use client_connections::{ClosedConnectionReceiver, ConnectionId, TransmissionLane};
|
||||
use futures::channel::mpsc;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{Future, Stream, StreamExt};
|
||||
@@ -16,7 +17,6 @@ use nymsphinx::forwarding::packet::MixPacket;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use nymsphinx::utils::sample_poisson_duration;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::VecDeque;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -27,22 +27,22 @@ use tokio::time;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_timer;
|
||||
|
||||
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
|
||||
// the available buffer space we want to take somewhat swift action, but we still need to give a
|
||||
// short time to give the channel a chance reduce pressure.
|
||||
const INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 1;
|
||||
// The minimum time between decreasing the average delay between packets. We don't want to change
|
||||
// to quickly to keep things somewhat stable. Also there are buffers downstreams meaning we need to
|
||||
// wait a little to see the effect before we decrease further.
|
||||
const DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 30;
|
||||
// If we enough time passes without any sign of backpressure in the channel, we can consider
|
||||
// lowering the average delay. The goal is to keep somewhat stable, rather than maxing out
|
||||
// bandwidth at all times.
|
||||
const ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS: u64 = 30;
|
||||
// The maximum multiplier we apply to the base average Poisson delay.
|
||||
const MAX_DELAY_MULTIPLIER: u32 = 6;
|
||||
// The minium multiplier we apply to the base average Poisson delay.
|
||||
const MIN_DELAY_MULTIPLIER: u32 = 1;
|
||||
use self::{
|
||||
sending_delay_controller::SendingDelayController, transmission_buffer::TransmissionBuffer,
|
||||
};
|
||||
|
||||
mod sending_delay_controller;
|
||||
mod transmission_buffer;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn get_time_now() -> time::Instant {
|
||||
time::Instant::now()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn get_time_now() -> wasm_timer::Instant {
|
||||
wasm_timer::Instant::now()
|
||||
}
|
||||
|
||||
/// Configurable parameters of the `OutQueueControl`
|
||||
pub(crate) struct Config {
|
||||
@@ -85,101 +85,6 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
struct SendingDelayController {
|
||||
/// Multiply the average sending delay.
|
||||
/// This is normally set to unity, but if we detect backpressure we increase this
|
||||
/// multiplier. We use discrete steps.
|
||||
current_multiplier: u32,
|
||||
|
||||
/// Maximum delay multiplier
|
||||
upper_bound: u32,
|
||||
|
||||
/// Minimum delay multiplier
|
||||
lower_bound: u32,
|
||||
|
||||
/// To make sure we don't change the multiplier to fast, we limit a change to some duration
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
time_when_changed: time::Instant,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
time_when_changed: wasm_timer::Instant,
|
||||
|
||||
/// If we have a long enough time without any backpressure detected we try reducing the sending
|
||||
/// delay multiplier
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
time_when_backpressure_detected: time::Instant,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
time_when_backpressure_detected: wasm_timer::Instant,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn get_time_now() -> time::Instant {
|
||||
time::Instant::now()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn get_time_now() -> wasm_timer::Instant {
|
||||
wasm_timer::Instant::now()
|
||||
}
|
||||
|
||||
impl SendingDelayController {
|
||||
fn new(lower_bound: u32, upper_bound: u32) -> Self {
|
||||
assert!(lower_bound <= upper_bound);
|
||||
let now = get_time_now();
|
||||
SendingDelayController {
|
||||
current_multiplier: MIN_DELAY_MULTIPLIER,
|
||||
upper_bound,
|
||||
lower_bound,
|
||||
time_when_changed: now,
|
||||
time_when_backpressure_detected: now,
|
||||
}
|
||||
}
|
||||
|
||||
fn current_multiplier(&self) -> u32 {
|
||||
self.current_multiplier
|
||||
}
|
||||
|
||||
fn increase_delay_multiplier(&mut self) {
|
||||
self.current_multiplier =
|
||||
(self.current_multiplier + 1).clamp(self.lower_bound, self.upper_bound);
|
||||
self.time_when_changed = get_time_now();
|
||||
log::debug!(
|
||||
"Increasing sending delay multiplier to: {}",
|
||||
self.current_multiplier
|
||||
);
|
||||
}
|
||||
|
||||
fn decrease_delay_multiplier(&mut self) {
|
||||
self.current_multiplier =
|
||||
(self.current_multiplier - 1).clamp(self.lower_bound, self.upper_bound);
|
||||
self.time_when_changed = get_time_now();
|
||||
log::debug!(
|
||||
"Decreasing sending delay multiplier to: {}",
|
||||
self.current_multiplier
|
||||
);
|
||||
}
|
||||
|
||||
fn record_backpressure_detected(&mut self) {
|
||||
self.time_when_backpressure_detected = get_time_now();
|
||||
}
|
||||
|
||||
fn not_increased_delay_recently(&self) -> bool {
|
||||
get_time_now()
|
||||
> self.time_when_changed + Duration::from_secs(INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
|
||||
}
|
||||
|
||||
fn is_sending_reliable(&self) -> bool {
|
||||
let now = get_time_now();
|
||||
let delay_change_interval = Duration::from_secs(DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS);
|
||||
let acceptable_time_without_backpressure =
|
||||
Duration::from_secs(ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS);
|
||||
|
||||
now > self.time_when_backpressure_detected + acceptable_time_without_backpressure
|
||||
&& now > self.time_when_changed + delay_change_interval
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct OutQueueControl<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
@@ -203,7 +108,7 @@ where
|
||||
|
||||
// To make sure we don't overload the mix_tx channel, we limit the rate we are pushing
|
||||
// messages.
|
||||
sending_rate_controller: SendingDelayController,
|
||||
sending_delay_controller: SendingDelayController,
|
||||
|
||||
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
|
||||
/// out to the network without any further delays.
|
||||
@@ -222,8 +127,13 @@ where
|
||||
/// Accessor to the common instance of network topology.
|
||||
topology_access: TopologyAccessor,
|
||||
|
||||
/// Buffer containing all real messages received. It is first exhausted before more are pulled.
|
||||
received_buffer: VecDeque<RealMessage>,
|
||||
/// Buffer containing all incoming real messages keyed by transmission lane, that we will send
|
||||
/// out to the mixnet.
|
||||
transmission_buffer: TransmissionBuffer,
|
||||
|
||||
/// Incoming channel for being notified of closed connections, so that we can close lanes
|
||||
/// corresponding to connections. To avoid sending traffic unnecessary
|
||||
closed_connection_rx: ClosedConnectionReceiver,
|
||||
}
|
||||
|
||||
pub(crate) struct RealMessage {
|
||||
@@ -242,8 +152,9 @@ impl RealMessage {
|
||||
|
||||
// messages are already prepared, etc. the real point of it is to forward it to mix_traffic
|
||||
// after sufficient delay
|
||||
pub(crate) type BatchRealMessageSender = mpsc::UnboundedSender<Vec<RealMessage>>;
|
||||
type BatchRealMessageReceiver = mpsc::UnboundedReceiver<Vec<RealMessage>>;
|
||||
pub(crate) type BatchRealMessageSender =
|
||||
mpsc::UnboundedSender<(Vec<RealMessage>, TransmissionLane)>;
|
||||
type BatchRealMessageReceiver = mpsc::UnboundedReceiver<(Vec<RealMessage>, TransmissionLane)>;
|
||||
|
||||
pub(crate) enum StreamMessage {
|
||||
Cover,
|
||||
@@ -266,22 +177,21 @@ where
|
||||
rng: R,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor,
|
||||
closed_connection_rx: ClosedConnectionReceiver,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
config,
|
||||
ack_key,
|
||||
sent_notifier,
|
||||
next_delay: None,
|
||||
sending_rate_controller: SendingDelayController::new(
|
||||
MIN_DELAY_MULTIPLIER,
|
||||
MAX_DELAY_MULTIPLIER,
|
||||
),
|
||||
sending_delay_controller: Default::default(),
|
||||
mix_tx,
|
||||
real_receiver,
|
||||
our_full_destination,
|
||||
rng,
|
||||
topology_access,
|
||||
received_buffer: VecDeque::with_capacity(0), // we won't be putting any data into this guy directly
|
||||
transmission_buffer: Default::default(),
|
||||
closed_connection_rx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,6 +256,10 @@ where
|
||||
self.sent_notify(fragment_id);
|
||||
}
|
||||
|
||||
// In addition to closing connections on receiving messages throught closed_connection_rx,
|
||||
// also close connections when sufficiently stale.
|
||||
self.transmission_buffer.prune_stale_connections();
|
||||
|
||||
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
|
||||
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
|
||||
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
|
||||
@@ -357,35 +271,41 @@ where
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
fn on_close_connection(&mut self, connection_id: ConnectionId) {
|
||||
log::debug!("Removing lane for connection: {connection_id}");
|
||||
self.transmission_buffer
|
||||
.remove(&TransmissionLane::ConnectionId(connection_id));
|
||||
}
|
||||
|
||||
fn current_average_message_sending_delay(&self) -> Duration {
|
||||
self.config.average_message_sending_delay
|
||||
* self.sending_rate_controller.current_multiplier()
|
||||
* self.sending_delay_controller.current_multiplier()
|
||||
}
|
||||
|
||||
fn adjust_current_average_message_sending_delay(&mut self) {
|
||||
let used_slots = self.mix_tx.max_capacity() - self.mix_tx.capacity();
|
||||
log::trace!(
|
||||
"used_slots: {used_slots}, current_multiplier: {}",
|
||||
self.sending_rate_controller.current_multiplier()
|
||||
self.sending_delay_controller.current_multiplier()
|
||||
);
|
||||
|
||||
// Even just a single used slot is enough to signal backpressure
|
||||
if used_slots > 0 {
|
||||
log::trace!("Backpressure detected");
|
||||
self.sending_rate_controller.record_backpressure_detected();
|
||||
self.sending_delay_controller.record_backpressure_detected();
|
||||
}
|
||||
|
||||
// If the buffer is running out, slow down the sending rate
|
||||
if self.mix_tx.capacity() == 0
|
||||
&& self.sending_rate_controller.not_increased_delay_recently()
|
||||
&& self.sending_delay_controller.not_increased_delay_recently()
|
||||
{
|
||||
self.sending_rate_controller.increase_delay_multiplier();
|
||||
self.sending_delay_controller.increase_delay_multiplier();
|
||||
}
|
||||
|
||||
// Very carefully step up the sending rate in case it seems like we can solidly handle the
|
||||
// current rate.
|
||||
if self.sending_rate_controller.is_sending_reliable() {
|
||||
self.sending_rate_controller.decrease_delay_multiplier();
|
||||
if self.sending_delay_controller.is_sending_reliable() {
|
||||
self.sending_delay_controller.decrease_delay_multiplier();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,6 +315,13 @@ where
|
||||
self.adjust_current_average_message_sending_delay();
|
||||
let avg_delay = self.current_average_message_sending_delay();
|
||||
|
||||
// Start by checking if we have any incoming messages about closed connections
|
||||
// NOTE: this feels a bit iffy, the `OutQueueControl` is getting ripe for a rewrite to
|
||||
// something simpler.
|
||||
if let Poll::Ready(Some(id)) = Pin::new(&mut self.closed_connection_rx).poll_next(cx) {
|
||||
self.on_close_connection(id);
|
||||
}
|
||||
|
||||
if let Some(ref mut next_delay) = &mut self.next_delay {
|
||||
// it is not yet time to return a message
|
||||
if next_delay.as_mut().poll(cx).is_pending() {
|
||||
@@ -419,28 +346,35 @@ where
|
||||
next_delay.as_mut().reset(next_poisson_delay);
|
||||
}
|
||||
|
||||
// check if we have anything immediately available
|
||||
if let Some(real_available) = self.received_buffer.pop_front() {
|
||||
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
|
||||
}
|
||||
|
||||
// decide what kind of message to send
|
||||
// On every iteration we get new messages from upstream. Given that these come bunched
|
||||
// in `Vec`, this ensures that on average we will fetch messages faster than we can
|
||||
// send, which is a condition for being able to multiplex sphinx packets from multiple
|
||||
// data streams.
|
||||
match Pin::new(&mut self.real_receiver).poll_next(cx) {
|
||||
// in the case our real message channel stream was closed, we should also indicate we are closed
|
||||
// (and whoever is using the stream should panic)
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
|
||||
// if there are more messages available, return first one and store the rest
|
||||
Poll::Ready(Some(real_messages)) => {
|
||||
self.received_buffer = real_messages.into();
|
||||
// we MUST HAVE received at least ONE message
|
||||
Poll::Ready(Some(StreamMessage::Real(Box::new(
|
||||
self.received_buffer.pop_front().unwrap(),
|
||||
))))
|
||||
Poll::Ready(Some((real_messages, conn_id))) => {
|
||||
log::trace!("handling real_messages: size: {}", real_messages.len());
|
||||
|
||||
self.transmission_buffer.store(&conn_id, real_messages);
|
||||
let real_next = self
|
||||
.transmission_buffer
|
||||
.pop_next_message_at_random()
|
||||
.expect("we just added one");
|
||||
|
||||
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
|
||||
}
|
||||
|
||||
// otherwise construct a dummy one
|
||||
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
|
||||
Poll::Pending => {
|
||||
if let Some(real_next) = self.transmission_buffer.pop_next_message_at_random() {
|
||||
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
|
||||
} else {
|
||||
// otherwise construct a dummy one
|
||||
Poll::Ready(Some(StreamMessage::Cover))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we never set an initial delay - let's do it now
|
||||
@@ -462,14 +396,9 @@ where
|
||||
}
|
||||
|
||||
fn poll_immediate(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
|
||||
// check if we have anything immediately available
|
||||
if let Some(real_available) = self.received_buffer.pop_front() {
|
||||
// if there are more messages immediately available, notify the runtime
|
||||
// because we should be polled again
|
||||
if !self.received_buffer.is_empty() {
|
||||
cx.waker().wake_by_ref()
|
||||
}
|
||||
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
|
||||
// Start by checking if we have any incoming messages about closed connections
|
||||
if let Poll::Ready(Some(id)) = Pin::new(&mut self.closed_connection_rx).poll_next(cx) {
|
||||
self.on_close_connection(id);
|
||||
}
|
||||
|
||||
match Pin::new(&mut self.real_receiver).poll_next(cx) {
|
||||
@@ -477,17 +406,26 @@ where
|
||||
// (and whoever is using the stream should panic)
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
|
||||
// if there are more messages available, return first one and store the rest
|
||||
Poll::Ready(Some(real_messages)) => {
|
||||
self.received_buffer = real_messages.into();
|
||||
// we MUST HAVE received at least ONE message
|
||||
Poll::Ready(Some(StreamMessage::Real(Box::new(
|
||||
self.received_buffer.pop_front().unwrap(),
|
||||
))))
|
||||
Poll::Ready(Some((real_messages, conn_id))) => {
|
||||
log::trace!("handling real_messages: size: {}", real_messages.len());
|
||||
|
||||
// First store what we got for the given connection id
|
||||
self.transmission_buffer.store(&conn_id, real_messages);
|
||||
let real_next = self
|
||||
.transmission_buffer
|
||||
.pop_next_message_at_random()
|
||||
.expect("we just added one");
|
||||
|
||||
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
|
||||
}
|
||||
|
||||
// if there's nothing, then there's nothing
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Pending => {
|
||||
if let Some(real_next) = self.transmission_buffer.pop_next_message_at_random() {
|
||||
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,24 +440,47 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn log_status(&self) {
|
||||
let packets = self.transmission_buffer.total_size();
|
||||
let backlog = self.transmission_buffer.total_size_in_bytes() as f64 / 1024.0;
|
||||
let lanes = self.transmission_buffer.num_lanes();
|
||||
let mult = self.sending_delay_controller.current_multiplier();
|
||||
let delay = self.current_average_message_sending_delay().as_millis();
|
||||
if self.config.disable_poisson_packet_distribution {
|
||||
log::info!(
|
||||
"Status: {lanes} lanes, backlog: {:.2} kiB ({packets}), no delay",
|
||||
backlog
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
"Status: {lanes} lanes, backlog: {:.2} kiB ({packets}), avg delay: {}ms ({mult})",
|
||||
backlog,
|
||||
delay
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
debug!("Started OutQueueControl with graceful shutdown support");
|
||||
|
||||
let mut status_timer = tokio::time::interval(Duration::from_secs(1));
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("OutQueueControl: Received shutdown");
|
||||
}
|
||||
next_message = self.next() => match next_message {
|
||||
Some(next_message) => {
|
||||
self.on_message(next_message).await;
|
||||
},
|
||||
None => {
|
||||
log::trace!("OutQueueControl: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
_ = status_timer.tick() => {
|
||||
self.log_status();
|
||||
}
|
||||
next_message = self.next() => if let Some(next_message) = next_message {
|
||||
self.on_message(next_message).await;
|
||||
} else {
|
||||
log::trace!("OutQueueControl: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::get_time_now;
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_timer;
|
||||
|
||||
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
|
||||
// the available buffer space we want to take somewhat swift action, but we still need to give a
|
||||
// short time to give the channel a chance reduce pressure.
|
||||
const INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 1;
|
||||
// The minimum time between decreasing the average delay between packets. We don't want to change
|
||||
// to quickly to keep things somewhat stable. Also there are buffers downstreams meaning we need to
|
||||
// wait a little to see the effect before we decrease further.
|
||||
const DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 30;
|
||||
// If we enough time passes without any sign of backpressure in the channel, we can consider
|
||||
// lowering the average delay. The goal is to keep somewhat stable, rather than maxing out
|
||||
// bandwidth at all times.
|
||||
const ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS: u64 = 30;
|
||||
// The maximum multiplier we apply to the base average Poisson delay.
|
||||
const MAX_DELAY_MULTIPLIER: u32 = 6;
|
||||
// The minium multiplier we apply to the base average Poisson delay.
|
||||
const MIN_DELAY_MULTIPLIER: u32 = 1;
|
||||
|
||||
pub(crate) struct SendingDelayController {
|
||||
/// Multiply the average sending delay.
|
||||
/// This is normally set to unity, but if we detect backpressure we increase this
|
||||
/// multiplier. We use discrete steps.
|
||||
current_multiplier: u32,
|
||||
|
||||
/// Maximum delay multiplier
|
||||
upper_bound: u32,
|
||||
|
||||
/// Minimum delay multiplier
|
||||
lower_bound: u32,
|
||||
|
||||
/// To make sure we don't change the multiplier to fast, we limit a change to some duration
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
time_when_changed: time::Instant,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
time_when_changed: wasm_timer::Instant,
|
||||
|
||||
/// If we have a long enough time without any backpressure detected we try reducing the sending
|
||||
/// delay multiplier
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
time_when_backpressure_detected: time::Instant,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
time_when_backpressure_detected: wasm_timer::Instant,
|
||||
}
|
||||
|
||||
impl Default for SendingDelayController {
|
||||
fn default() -> Self {
|
||||
SendingDelayController::new(MIN_DELAY_MULTIPLIER, MAX_DELAY_MULTIPLIER)
|
||||
}
|
||||
}
|
||||
|
||||
impl SendingDelayController {
|
||||
pub(crate) fn new(lower_bound: u32, upper_bound: u32) -> Self {
|
||||
assert!(lower_bound <= upper_bound);
|
||||
let now = get_time_now();
|
||||
SendingDelayController {
|
||||
current_multiplier: MIN_DELAY_MULTIPLIER,
|
||||
upper_bound,
|
||||
lower_bound,
|
||||
time_when_changed: now,
|
||||
time_when_backpressure_detected: now,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn current_multiplier(&self) -> u32 {
|
||||
self.current_multiplier
|
||||
}
|
||||
|
||||
pub(crate) fn increase_delay_multiplier(&mut self) {
|
||||
if self.current_multiplier < self.upper_bound {
|
||||
self.current_multiplier =
|
||||
(self.current_multiplier + 1).clamp(self.lower_bound, self.upper_bound);
|
||||
self.time_when_changed = get_time_now();
|
||||
log::debug!(
|
||||
"Increasing sending delay multiplier to: {}",
|
||||
self.current_multiplier
|
||||
);
|
||||
} else {
|
||||
log::warn!("Trying to increase delay multipler higher than allowed");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn decrease_delay_multiplier(&mut self) {
|
||||
if self.current_multiplier > self.lower_bound {
|
||||
self.current_multiplier =
|
||||
(self.current_multiplier - 1).clamp(self.lower_bound, self.upper_bound);
|
||||
self.time_when_changed = get_time_now();
|
||||
log::debug!(
|
||||
"Decreasing sending delay multiplier to: {}",
|
||||
self.current_multiplier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn record_backpressure_detected(&mut self) {
|
||||
self.time_when_backpressure_detected = get_time_now();
|
||||
}
|
||||
|
||||
pub(crate) fn not_increased_delay_recently(&self) -> bool {
|
||||
get_time_now()
|
||||
> self.time_when_changed + Duration::from_secs(INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
|
||||
}
|
||||
|
||||
pub(crate) fn is_sending_reliable(&self) -> bool {
|
||||
let now = get_time_now();
|
||||
let delay_change_interval = Duration::from_secs(DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS);
|
||||
let acceptable_time_without_backpressure =
|
||||
Duration::from_secs(ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS);
|
||||
|
||||
now > self.time_when_backpressure_detected + acceptable_time_without_backpressure
|
||||
&& now > self.time_when_changed + delay_change_interval
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use client_connections::TransmissionLane;
|
||||
use rand::seq::SliceRandom;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_timer;
|
||||
|
||||
use super::{get_time_now, RealMessage};
|
||||
|
||||
// The number of lanes included in the oldest set. Used when we need to prioritize traffic.
|
||||
const OLDEST_LANE_SET_SIZE: usize = 5;
|
||||
// As a way of prune connections we also check for timeouts.
|
||||
const MSG_CONSIDERED_STALE_AFTER_SECS: u64 = 10 * 60;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TransmissionBuffer {
|
||||
buffer: HashMap<TransmissionLane, LaneBufferEntry>,
|
||||
}
|
||||
|
||||
impl TransmissionBuffer {
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.buffer.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option<LaneBufferEntry> {
|
||||
self.buffer.remove(lane)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn num_lanes(&self) -> usize {
|
||||
self.buffer.keys().count()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn connections(&self) -> HashSet<u64> {
|
||||
self.buffer
|
||||
.keys()
|
||||
.filter_map(|lane| match lane {
|
||||
TransmissionLane::ConnectionId(id) => Some(id),
|
||||
_ => None,
|
||||
})
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn total_size(&self) -> usize {
|
||||
self.buffer.values().map(LaneBufferEntry::len).sum()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn total_size_in_bytes(&self) -> usize {
|
||||
self.buffer
|
||||
.values()
|
||||
.map(|lane_buffer_entry| {
|
||||
lane_buffer_entry
|
||||
.real_messages
|
||||
.iter()
|
||||
.map(|real_message| real_message.mix_packet.sphinx_packet().len())
|
||||
.sum::<usize>()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn get_oldest_set(&self) -> Vec<TransmissionLane> {
|
||||
let mut buffer: Vec<_> = self
|
||||
.buffer
|
||||
.iter()
|
||||
.map(|(k, v)| (k, v.messages_transmitted))
|
||||
.collect();
|
||||
buffer.sort_by_key(|v| v.1);
|
||||
buffer
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|(k, _)| *k)
|
||||
.take(OLDEST_LANE_SET_SIZE)
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn store(&mut self, lane: &TransmissionLane, real_messages: Vec<RealMessage>) {
|
||||
if let Some(lane_buffer_entry) = self.buffer.get_mut(lane) {
|
||||
lane_buffer_entry.append(real_messages);
|
||||
} else {
|
||||
self.buffer
|
||||
.insert(*lane, LaneBufferEntry::new(real_messages));
|
||||
}
|
||||
}
|
||||
|
||||
fn pick_random_lane(&self) -> Option<&TransmissionLane> {
|
||||
let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect();
|
||||
lanes.choose(&mut rand::thread_rng()).copied()
|
||||
}
|
||||
|
||||
fn pick_random_small_lane(&self) -> Option<&TransmissionLane> {
|
||||
let lanes: Vec<&TransmissionLane> = self
|
||||
.buffer
|
||||
.iter()
|
||||
.filter(|(_, v)| v.is_small())
|
||||
.map(|(k, _)| k)
|
||||
.collect();
|
||||
lanes.choose(&mut rand::thread_rng()).copied()
|
||||
}
|
||||
|
||||
fn pick_random_old_lane(&self) -> Option<TransmissionLane> {
|
||||
let lanes = self.get_oldest_set();
|
||||
lanes.choose(&mut rand::thread_rng()).copied()
|
||||
}
|
||||
|
||||
fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option<RealMessage> {
|
||||
let real_msgs_queued = self.buffer.get_mut(lane)?;
|
||||
let real_next = real_msgs_queued.pop_front()?;
|
||||
real_msgs_queued.messages_transmitted += 1;
|
||||
if real_msgs_queued.is_empty() {
|
||||
self.buffer.remove(lane);
|
||||
}
|
||||
Some(real_next)
|
||||
}
|
||||
|
||||
pub(crate) fn pop_next_message_at_random(&mut self) -> Option<RealMessage> {
|
||||
if self.buffer.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Very basic heuristic where we prioritize according to small lanes first, the older lanes
|
||||
// to try to finish lanes when possible, then the rest.
|
||||
let lane = if let Some(small_lane) = self.pick_random_small_lane() {
|
||||
*small_lane
|
||||
} else if let Some(old_lane) = self.pick_random_old_lane() {
|
||||
old_lane
|
||||
} else {
|
||||
*self.pick_random_lane()?
|
||||
};
|
||||
|
||||
log::trace!("picking to send from lane: {:?}", lane);
|
||||
self.pop_front_from_lane(&lane)
|
||||
}
|
||||
|
||||
pub(crate) fn prune_stale_connections(&mut self) {
|
||||
let stale_entries: Vec<_> = self
|
||||
.buffer
|
||||
.iter()
|
||||
.filter_map(|(lane, entry)| if entry.is_stale() { Some(lane) } else { None })
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
for lane in stale_entries {
|
||||
self.remove(&lane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct LaneBufferEntry {
|
||||
pub real_messages: VecDeque<RealMessage>,
|
||||
pub messages_transmitted: usize,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub time_for_last_activity: time::Instant,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub time_for_last_activity: wasm_timer::Instant,
|
||||
}
|
||||
|
||||
impl LaneBufferEntry {
|
||||
fn new(real_messages: Vec<RealMessage>) -> Self {
|
||||
LaneBufferEntry {
|
||||
real_messages: real_messages.into(),
|
||||
messages_transmitted: 0,
|
||||
time_for_last_activity: get_time_now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn append(&mut self, real_messages: Vec<RealMessage>) {
|
||||
self.real_messages.append(&mut real_messages.into());
|
||||
self.time_for_last_activity = get_time_now();
|
||||
}
|
||||
|
||||
fn pop_front(&mut self) -> Option<RealMessage> {
|
||||
self.real_messages.pop_front()
|
||||
}
|
||||
|
||||
fn is_small(&self) -> bool {
|
||||
self.real_messages.len() < 100
|
||||
}
|
||||
|
||||
fn is_stale(&self) -> bool {
|
||||
get_time_now() - self.time_for_last_activity
|
||||
> Duration::from_secs(MSG_CONSIDERED_STALE_AFTER_SECS)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn len(&self) -> usize {
|
||||
self.real_messages.len()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.real_messages.is_empty()
|
||||
}
|
||||
}
|
||||
@@ -208,7 +208,7 @@ impl ReceivedMessagesBuffer {
|
||||
}
|
||||
|
||||
async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) {
|
||||
debug!(
|
||||
trace!(
|
||||
"Processing {:?} new message that might get added to the buffer!",
|
||||
msgs.len()
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ cfg_if::cfg_if! {
|
||||
#[clap(author = "Nymtech", version, about)]
|
||||
struct Cli {
|
||||
/// Path pointing to an env file that configures the client.
|
||||
#[clap(long)]
|
||||
#[clap(short, long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
/// Path where the sqlite credental database will be located.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
@@ -33,6 +33,7 @@ tokio-tungstenite = "0.14" # websocket
|
||||
|
||||
## internal
|
||||
client-core = { path = "../client-core" }
|
||||
client-connections = { path = "../../common/client-connections" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
config = { path = "../../common/config" }
|
||||
completions = { path = "../../common/completions" }
|
||||
|
||||
@@ -43,6 +43,7 @@ async fn send_file_with_reply() {
|
||||
recipient,
|
||||
message: read_data,
|
||||
with_reply_surb: true,
|
||||
connection_id: 0,
|
||||
};
|
||||
|
||||
println!("sending content of 'dummy_file' over the mix network...");
|
||||
@@ -91,6 +92,7 @@ async fn send_file_without_reply() {
|
||||
recipient,
|
||||
message: read_data,
|
||||
with_reply_surb: false,
|
||||
connection_id: 0,
|
||||
};
|
||||
|
||||
println!("sending content of 'dummy_file' over the mix network...");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use client_connections::{ClosedConnectionReceiver, ClosedConnectionSender, TransmissionLane};
|
||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use client_core::client::inbound_messages::{
|
||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||
@@ -110,6 +111,7 @@ impl NymClient {
|
||||
stream.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn start_real_traffic_controller(
|
||||
&self,
|
||||
topology_accessor: TopologyAccessor,
|
||||
@@ -117,6 +119,7 @@ impl NymClient {
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
input_receiver: InputMessageReceiver,
|
||||
mix_sender: BatchMixMessageSender,
|
||||
closed_connection_rx: ClosedConnectionReceiver,
|
||||
shutdown: ShutdownListener,
|
||||
) {
|
||||
let mut controller_config = real_messages_control::Config::new(
|
||||
@@ -146,6 +149,7 @@ impl NymClient {
|
||||
mix_sender,
|
||||
topology_accessor,
|
||||
reply_key_storage,
|
||||
closed_connection_rx,
|
||||
)
|
||||
.start_with_shutdown(shutdown);
|
||||
}
|
||||
@@ -279,11 +283,16 @@ impl NymClient {
|
||||
&self,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
msg_input: InputMessageSender,
|
||||
closed_connection_tx: ClosedConnectionSender,
|
||||
) {
|
||||
info!("Starting websocket listener...");
|
||||
|
||||
let websocket_handler =
|
||||
websocket::Handler::new(msg_input, buffer_requester, self.as_mix_recipient());
|
||||
let websocket_handler = websocket::Handler::new(
|
||||
msg_input,
|
||||
closed_connection_tx,
|
||||
buffer_requester,
|
||||
&self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
|
||||
}
|
||||
@@ -292,7 +301,8 @@ impl NymClient {
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub fn send_message(&mut self, recipient: Recipient, message: Vec<u8>, with_reply_surb: bool) {
|
||||
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
|
||||
let lane = TransmissionLane::General;
|
||||
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb, lane);
|
||||
|
||||
self.input_tx
|
||||
.as_ref()
|
||||
@@ -403,12 +413,17 @@ impl NymClient {
|
||||
let sphinx_message_sender =
|
||||
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
|
||||
|
||||
// Channels that the websocket listener can use to signal downstream to the real traffic
|
||||
// controller that connections are closed.
|
||||
let (closed_connection_tx, closed_connection_rx) = mpsc::unbounded();
|
||||
|
||||
self.start_real_traffic_controller(
|
||||
shared_topology_accessor.clone(),
|
||||
reply_key_storage,
|
||||
ack_receiver,
|
||||
input_receiver,
|
||||
sphinx_message_sender.clone(),
|
||||
closed_connection_rx,
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
|
||||
@@ -425,9 +440,11 @@ impl NymClient {
|
||||
}
|
||||
|
||||
match self.config.get_socket_type() {
|
||||
SocketType::WebSocket => {
|
||||
self.start_websocket_listener(received_buffer_request_sender, input_sender)
|
||||
}
|
||||
SocketType::WebSocket => self.start_websocket_listener(
|
||||
received_buffer_request_sender,
|
||||
input_sender,
|
||||
closed_connection_tx,
|
||||
),
|
||||
SocketType::None => {
|
||||
// if we did not start the socket, it means we're running (supposedly) in the native mode
|
||||
// and hence we should announce 'ourselves' to the buffer
|
||||
|
||||
@@ -50,7 +50,7 @@ fn long_version_static() -> &'static str {
|
||||
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
/// Path pointing to an env file that configures the client.
|
||||
#[clap(long)]
|
||||
#[clap(short, long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
#[clap(subcommand)]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use client_connections::{ClosedConnectionSender, TransmissionLane};
|
||||
use client_core::client::{
|
||||
inbound_messages::{InputMessage, InputMessageSender},
|
||||
received_buffer::{
|
||||
@@ -34,6 +35,7 @@ impl Default for ReceivedResponseType {
|
||||
|
||||
pub(crate) struct Handler {
|
||||
msg_input: InputMessageSender,
|
||||
closed_connection_tx: ClosedConnectionSender,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
self_full_address: Recipient,
|
||||
socket: Option<WebSocketStream<TcpStream>>,
|
||||
@@ -45,6 +47,7 @@ impl Clone for Handler {
|
||||
fn clone(&self) -> Self {
|
||||
Handler {
|
||||
msg_input: self.msg_input.clone(),
|
||||
closed_connection_tx: self.closed_connection_tx.clone(),
|
||||
buffer_requester: self.buffer_requester.clone(),
|
||||
self_full_address: self.self_full_address,
|
||||
socket: None,
|
||||
@@ -64,13 +67,15 @@ impl Drop for Handler {
|
||||
impl Handler {
|
||||
pub(crate) fn new(
|
||||
msg_input: InputMessageSender,
|
||||
closed_connection_tx: ClosedConnectionSender,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
self_full_address: Recipient,
|
||||
self_full_address: &Recipient,
|
||||
) -> Self {
|
||||
Handler {
|
||||
msg_input,
|
||||
closed_connection_tx,
|
||||
buffer_requester,
|
||||
self_full_address,
|
||||
self_full_address: *self_full_address,
|
||||
socket: None,
|
||||
received_response_type: Default::default(),
|
||||
}
|
||||
@@ -78,12 +83,14 @@ impl Handler {
|
||||
|
||||
fn handle_send(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
recipient: &Recipient,
|
||||
message: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
connection_id: u64,
|
||||
) -> Option<ServerResponse> {
|
||||
// the ack control is now responsible for chunking, etc.
|
||||
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
|
||||
let lane = TransmissionLane::ConnectionId(connection_id);
|
||||
let input_msg = InputMessage::new_fresh(*recipient, message, with_reply_surb, lane);
|
||||
self.msg_input.unbounded_send(input_msg).unwrap();
|
||||
|
||||
None
|
||||
@@ -104,18 +111,27 @@ impl Handler {
|
||||
ServerResponse::SelfAddress(self.self_full_address)
|
||||
}
|
||||
|
||||
fn handle_closed_connection(&self, connection_id: u64) -> Option<ServerResponse> {
|
||||
self.closed_connection_tx
|
||||
.unbounded_send(connection_id)
|
||||
.unwrap();
|
||||
None
|
||||
}
|
||||
|
||||
fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
|
||||
match request {
|
||||
ClientRequest::Send {
|
||||
recipient,
|
||||
message,
|
||||
with_reply_surb,
|
||||
} => self.handle_send(recipient, message, with_reply_surb),
|
||||
connection_id,
|
||||
} => self.handle_send(&recipient, message, with_reply_surb, connection_id),
|
||||
ClientRequest::Reply {
|
||||
message,
|
||||
reply_surb,
|
||||
} => self.handle_reply(reply_surb, message),
|
||||
ClientRequest::SelfAddress => Some(self.handle_self_address()),
|
||||
ClientRequest::ClosedConnection(id) => self.handle_closed_connection(id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +150,7 @@ impl Handler {
|
||||
response.map(|resp| WsMessage::text(resp.into_text()))
|
||||
}
|
||||
|
||||
fn handle_binary_message(&mut self, msg: Vec<u8>) -> Option<WsMessage> {
|
||||
fn handle_binary_message(&mut self, msg: &[u8]) -> Option<WsMessage> {
|
||||
debug!("Handling binary message request");
|
||||
|
||||
self.received_response_type = ReceivedResponseType::Binary;
|
||||
@@ -154,37 +170,11 @@ impl Handler {
|
||||
// old version of this file.
|
||||
match raw_request {
|
||||
WsMessage::Text(text_message) => self.handle_text_message(text_message),
|
||||
WsMessage::Binary(binary_message) => self.handle_binary_message(binary_message),
|
||||
WsMessage::Binary(binary_message) => self.handle_binary_message(&binary_message),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
|
||||
// let's just play along for now
|
||||
fn prepare_reconstructed_binary(
|
||||
&self,
|
||||
reconstructed_messages: Vec<ReconstructedMessage>,
|
||||
) -> Vec<Result<WsMessage, WsError>> {
|
||||
reconstructed_messages
|
||||
.into_iter()
|
||||
.map(ServerResponse::Received)
|
||||
.map(|resp| Ok(WsMessage::Binary(resp.into_binary())))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
|
||||
// let's just play along for now
|
||||
fn prepare_reconstructed_text(
|
||||
&self,
|
||||
reconstructed_messages: Vec<ReconstructedMessage>,
|
||||
) -> Vec<Result<WsMessage, WsError>> {
|
||||
reconstructed_messages
|
||||
.into_iter()
|
||||
.map(ServerResponse::Received)
|
||||
.map(|resp| Ok(WsMessage::Text(resp.into_text())))
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn push_websocket_received_plaintexts(
|
||||
&mut self,
|
||||
reconstructed_messages: Vec<ReconstructedMessage>,
|
||||
@@ -193,10 +183,8 @@ impl Handler {
|
||||
// if it's text or binary, but for time being we use the naive assumption that if
|
||||
// client is sending Message::Text it expects text back. Same for Message::Binary
|
||||
let response_messages = match self.received_response_type {
|
||||
ReceivedResponseType::Binary => {
|
||||
self.prepare_reconstructed_binary(reconstructed_messages)
|
||||
}
|
||||
ReceivedResponseType::Text => self.prepare_reconstructed_text(reconstructed_messages),
|
||||
ReceivedResponseType::Binary => prepare_reconstructed_binary(reconstructed_messages),
|
||||
ReceivedResponseType::Text => prepare_reconstructed_text(reconstructed_messages),
|
||||
};
|
||||
|
||||
let mut send_stream = futures::stream::iter(response_messages);
|
||||
@@ -291,3 +279,27 @@ impl Handler {
|
||||
self.listen_for_requests(reconstructed_receiver).await;
|
||||
}
|
||||
}
|
||||
|
||||
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
|
||||
// let's just play along for now
|
||||
fn prepare_reconstructed_binary(
|
||||
reconstructed_messages: Vec<ReconstructedMessage>,
|
||||
) -> Vec<Result<WsMessage, WsError>> {
|
||||
reconstructed_messages
|
||||
.into_iter()
|
||||
.map(ServerResponse::Received)
|
||||
.map(|resp| Ok(WsMessage::Binary(resp.into_binary())))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
|
||||
// let's just play along for now
|
||||
fn prepare_reconstructed_text(
|
||||
reconstructed_messages: Vec<ReconstructedMessage>,
|
||||
) -> Vec<Result<WsMessage, WsError>> {
|
||||
reconstructed_messages
|
||||
.into_iter()
|
||||
.map(ServerResponse::Received)
|
||||
.map(|resp| Ok(WsMessage::Text(resp.into_text())))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ pub const REPLY_REQUEST_TAG: u8 = 0x01;
|
||||
/// Value tag representing [`SelfAddress`] variant of the [`ClientRequest`]
|
||||
pub const SELF_ADDRESS_REQUEST_TAG: u8 = 0x02;
|
||||
|
||||
/// Value tag representing [`ClosedConnection`] variant of the [`ClientRequest`]
|
||||
pub const CLOSED_CONNECTION_REQUEST_TAG: u8 = 0x03;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug)]
|
||||
pub enum ClientRequest {
|
||||
@@ -28,32 +31,41 @@ pub enum ClientRequest {
|
||||
message: Vec<u8>,
|
||||
// Perhaps we could change it to a number to indicate how many reply_SURBs we want to include?
|
||||
with_reply_surb: bool,
|
||||
connection_id: u64,
|
||||
},
|
||||
Reply {
|
||||
message: Vec<u8>,
|
||||
reply_surb: ReplySurb,
|
||||
},
|
||||
SelfAddress,
|
||||
ClosedConnection(u64),
|
||||
}
|
||||
|
||||
// we could have been parsing it directly TryFrom<WsMessage>, but we want to retain
|
||||
// information about whether it came from binary or text to send appropriate response back
|
||||
impl ClientRequest {
|
||||
// SEND_REQUEST_TAG || with_surb || recipient || data_len || data
|
||||
fn serialize_send(recipient: Recipient, data: Vec<u8>, with_reply_surb: bool) -> Vec<u8> {
|
||||
// SEND_REQUEST_TAG || with_surb || recipient || conn_id || data_len || data
|
||||
fn serialize_send(
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
connection_id: u64,
|
||||
) -> Vec<u8> {
|
||||
let data_len_bytes = (data.len() as u64).to_be_bytes();
|
||||
let conn_id_bytes = connection_id.to_be_bytes();
|
||||
std::iter::once(SEND_REQUEST_TAG)
|
||||
.chain(std::iter::once(with_reply_surb as u8))
|
||||
.chain(recipient.to_bytes().iter().cloned()) // will not be length prefixed because the length is constant
|
||||
.chain(conn_id_bytes.iter().cloned())
|
||||
.chain(data_len_bytes.iter().cloned())
|
||||
.chain(data.into_iter())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// SEND_REQUEST_TAG || with_reply || recipient || data_len || data
|
||||
// SEND_REQUEST_TAG || with_reply || recipient || conn_id || data_len || data
|
||||
fn deserialize_send(b: &[u8]) -> Result<Self, error::Error> {
|
||||
// we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + sizeof<u64> bytes
|
||||
if b.len() < 2 + Recipient::LEN + size_of::<u64>() {
|
||||
// we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + 2*sizeof<u64> bytes
|
||||
if b.len() < 2 + Recipient::LEN + 2 * size_of::<u64>() {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::TooShortRequest,
|
||||
"not enough data provided to recover 'send'".to_string(),
|
||||
@@ -86,9 +98,15 @@ impl ClientRequest {
|
||||
}
|
||||
};
|
||||
|
||||
let data_len_bytes = &b[2 + Recipient::LEN..2 + Recipient::LEN + size_of::<u64>()];
|
||||
let mut connection_id_bytes = [0u8; size_of::<u64>()];
|
||||
connection_id_bytes
|
||||
.copy_from_slice(&b[2 + Recipient::LEN..2 + Recipient::LEN + size_of::<u64>()]);
|
||||
let connection_id = u64::from_be_bytes(connection_id_bytes);
|
||||
|
||||
let data_len_bytes =
|
||||
&b[2 + Recipient::LEN + size_of::<u64>()..2 + Recipient::LEN + 2 * size_of::<u64>()];
|
||||
let data_len = u64::from_be_bytes(data_len_bytes.try_into().unwrap());
|
||||
let data = &b[2 + Recipient::LEN + size_of::<u64>()..];
|
||||
let data = &b[2 + Recipient::LEN + 2 * size_of::<u64>()..];
|
||||
if data.len() as u64 != data_len {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedRequest,
|
||||
@@ -104,11 +122,12 @@ impl ClientRequest {
|
||||
with_reply_surb,
|
||||
recipient,
|
||||
message: data.to_vec(),
|
||||
connection_id,
|
||||
})
|
||||
}
|
||||
|
||||
// REPLY_REQUEST_TAG || surb_len || surb || message_len || message
|
||||
fn serialize_reply(message: Vec<u8>, reply_surb: ReplySurb) -> Vec<u8> {
|
||||
fn serialize_reply(message: Vec<u8>, reply_surb: &ReplySurb) -> Vec<u8> {
|
||||
let reply_surb_bytes = reply_surb.to_bytes();
|
||||
let surb_len_bytes = (reply_surb_bytes.len() as u64).to_be_bytes();
|
||||
let message_len_bytes = (message.len() as u64).to_be_bytes();
|
||||
@@ -202,20 +221,43 @@ impl ClientRequest {
|
||||
ClientRequest::SelfAddress
|
||||
}
|
||||
|
||||
// CLOSED_CONNECTION_REQUEST_TAG
|
||||
fn serialize_closed_connection(connection_id: u64) -> Vec<u8> {
|
||||
let conn_id_bytes = connection_id.to_be_bytes();
|
||||
std::iter::once(CLOSED_CONNECTION_REQUEST_TAG)
|
||||
.chain(conn_id_bytes.iter().copied())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// CLOSED_CONNECTION_REQUEST_TAG
|
||||
fn deserialize_closed_connection(b: &[u8]) -> Self {
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], CLOSED_CONNECTION_REQUEST_TAG);
|
||||
|
||||
let mut connection_id_bytes = [0u8; size_of::<u64>()];
|
||||
connection_id_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
|
||||
let connection_id = u64::from_be_bytes(connection_id_bytes);
|
||||
|
||||
ClientRequest::ClosedConnection(connection_id)
|
||||
}
|
||||
|
||||
pub fn serialize(self) -> Vec<u8> {
|
||||
match self {
|
||||
ClientRequest::Send {
|
||||
recipient,
|
||||
message,
|
||||
with_reply_surb,
|
||||
} => Self::serialize_send(recipient, message, with_reply_surb),
|
||||
connection_id,
|
||||
} => Self::serialize_send(recipient, message, with_reply_surb, connection_id),
|
||||
|
||||
ClientRequest::Reply {
|
||||
message,
|
||||
reply_surb,
|
||||
} => Self::serialize_reply(message, reply_surb),
|
||||
} => Self::serialize_reply(message, &reply_surb),
|
||||
|
||||
ClientRequest::SelfAddress => Self::serialize_self_address(),
|
||||
|
||||
ClientRequest::ClosedConnection(id) => Self::serialize_closed_connection(id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,15 +287,16 @@ impl ClientRequest {
|
||||
SEND_REQUEST_TAG => Self::deserialize_send(b),
|
||||
REPLY_REQUEST_TAG => Self::deserialize_reply(b),
|
||||
SELF_ADDRESS_REQUEST_TAG => Ok(Self::deserialize_self_address(b)),
|
||||
CLOSED_CONNECTION_REQUEST_TAG => Ok(Self::deserialize_closed_connection(b)),
|
||||
n => Err(error::Error::new(
|
||||
ErrorKind::UnknownRequest,
|
||||
format!("type {}", n),
|
||||
format!("type {n}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_binary(raw_req: Vec<u8>) -> Result<Self, error::Error> {
|
||||
Self::deserialize(&raw_req)
|
||||
pub fn try_from_binary(raw_req: &[u8]) -> Result<Self, error::Error> {
|
||||
Self::deserialize(raw_req)
|
||||
}
|
||||
|
||||
pub fn try_from_text(raw_req: String) -> Result<Self, error::Error> {
|
||||
@@ -280,6 +323,7 @@ mod tests {
|
||||
recipient,
|
||||
message: b"foomp".to_vec(),
|
||||
with_reply_surb: false,
|
||||
connection_id: 42,
|
||||
};
|
||||
|
||||
let bytes = send_request_no_surb.serialize();
|
||||
@@ -289,10 +333,12 @@ mod tests {
|
||||
recipient,
|
||||
message,
|
||||
with_reply_surb,
|
||||
connection_id,
|
||||
} => {
|
||||
assert_eq!(recipient.to_string(), recipient_string);
|
||||
assert_eq!(message, b"foomp".to_vec());
|
||||
assert!(!with_reply_surb)
|
||||
assert!(!with_reply_surb);
|
||||
assert_eq!(connection_id, 42)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -301,6 +347,7 @@ mod tests {
|
||||
recipient,
|
||||
message: b"foomp".to_vec(),
|
||||
with_reply_surb: true,
|
||||
connection_id: 213,
|
||||
};
|
||||
|
||||
let bytes = send_request_surb.serialize();
|
||||
@@ -310,10 +357,12 @@ mod tests {
|
||||
recipient,
|
||||
message,
|
||||
with_reply_surb,
|
||||
connection_id,
|
||||
} => {
|
||||
assert_eq!(recipient.to_string(), recipient_string);
|
||||
assert_eq!(message, b"foomp".to_vec());
|
||||
assert!(with_reply_surb)
|
||||
assert!(with_reply_surb);
|
||||
assert_eq!(connection_id, 213)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -352,4 +401,15 @@ mod tests {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_connection_request_serialization_works() {
|
||||
let close_connection_request = ClientRequest::ClosedConnection(42);
|
||||
let bytes = close_connection_request.serialize();
|
||||
let recovered = ClientRequest::deserialize(&bytes).unwrap();
|
||||
match recovered {
|
||||
ClientRequest::ClosedConnection(id) => assert_eq!(id, 42),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ pub(super) enum ClientRequestText {
|
||||
message: String,
|
||||
recipient: String,
|
||||
with_reply_surb: bool,
|
||||
connection_id: u64,
|
||||
},
|
||||
SelfAddress,
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -46,6 +47,7 @@ impl TryInto<ClientRequest> for ClientRequestText {
|
||||
message,
|
||||
recipient,
|
||||
with_reply_surb,
|
||||
connection_id,
|
||||
} => {
|
||||
let message_bytes = message.into_bytes();
|
||||
let recipient = Recipient::try_from_base58_string(recipient).map_err(|err| {
|
||||
@@ -56,6 +58,7 @@ impl TryInto<ClientRequest> for ClientRequestText {
|
||||
message: message_bytes,
|
||||
recipient,
|
||||
with_reply_surb,
|
||||
connection_id,
|
||||
})
|
||||
}
|
||||
ClientRequestText::SelfAddress => Ok(ClientRequest::SelfAddress),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
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"
|
||||
@@ -26,6 +26,7 @@ url = "2.2"
|
||||
|
||||
# internal
|
||||
client-core = { path = "../client-core" }
|
||||
client-connections = { path = "../../common/client-connections" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
config = { path = "../../common/config" }
|
||||
completions = { path = "../../common/completions" }
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::socks::{
|
||||
authentication::{AuthenticationMethods, Authenticator, User},
|
||||
server::SphinxSocksServer,
|
||||
};
|
||||
use client_connections::{ClosedConnectionReceiver, ClosedConnectionSender};
|
||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use client_core::client::inbound_messages::{
|
||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||
@@ -110,6 +111,7 @@ impl NymClient {
|
||||
stream.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn start_real_traffic_controller(
|
||||
&self,
|
||||
topology_accessor: TopologyAccessor,
|
||||
@@ -117,6 +119,7 @@ impl NymClient {
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
input_receiver: InputMessageReceiver,
|
||||
mix_sender: BatchMixMessageSender,
|
||||
closed_connection_rx: ClosedConnectionReceiver,
|
||||
shutdown: ShutdownListener,
|
||||
) {
|
||||
let mut controller_config = client_core::client::real_messages_control::Config::new(
|
||||
@@ -146,6 +149,7 @@ impl NymClient {
|
||||
mix_sender,
|
||||
topology_accessor,
|
||||
reply_key_storage,
|
||||
closed_connection_rx,
|
||||
)
|
||||
.start_with_shutdown(shutdown);
|
||||
}
|
||||
@@ -279,6 +283,7 @@ impl NymClient {
|
||||
&self,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
msg_input: InputMessageSender,
|
||||
closed_connection_tx: ClosedConnectionSender,
|
||||
shutdown: ShutdownListener,
|
||||
) {
|
||||
info!("Starting socks5 listener...");
|
||||
@@ -293,7 +298,11 @@ impl NymClient {
|
||||
self.as_mix_recipient(),
|
||||
shutdown,
|
||||
);
|
||||
tokio::spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
|
||||
tokio::spawn(async move {
|
||||
sphinx_socks
|
||||
.serve(msg_input, buffer_requester, closed_connection_tx)
|
||||
.await
|
||||
});
|
||||
}
|
||||
|
||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||
@@ -396,12 +405,17 @@ impl NymClient {
|
||||
let sphinx_message_sender =
|
||||
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
|
||||
|
||||
// Channel for announcing closed (socks5) connections by the controller.
|
||||
// This will be forwarded to `OutQueueControl`
|
||||
let (closed_connection_tx, closed_connection_rx) = mpsc::unbounded();
|
||||
|
||||
self.start_real_traffic_controller(
|
||||
shared_topology_accessor.clone(),
|
||||
reply_key_storage,
|
||||
ack_receiver,
|
||||
input_receiver,
|
||||
sphinx_message_sender.clone(),
|
||||
closed_connection_rx,
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
|
||||
@@ -420,6 +434,7 @@ impl NymClient {
|
||||
self.start_socks5_listener(
|
||||
received_buffer_request_sender,
|
||||
input_sender,
|
||||
closed_connection_tx,
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ fn long_version_static() -> &'static str {
|
||||
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
/// Path pointing to an env file that configures the client.
|
||||
#[clap(long)]
|
||||
#[clap(short, long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
#[clap(subcommand)]
|
||||
@@ -62,8 +62,10 @@ pub(crate) struct Cli {
|
||||
pub(crate) enum Commands {
|
||||
/// Initialise a Nym client. Do this first!
|
||||
Init(init::Init),
|
||||
|
||||
/// Run the Nym client with provided configuration client optionally overriding set parameters
|
||||
Run(run::Run),
|
||||
|
||||
/// Try to upgrade the client
|
||||
Upgrade(upgrade::Upgrade),
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ use super::authentication::{AuthenticationMethods, Authenticator, User};
|
||||
use super::request::{SocksCommand, SocksRequest};
|
||||
use super::types::{ResponseCode, SocksProxyError};
|
||||
use super::{RESERVED, SOCKS_VERSION};
|
||||
use client_core::client::inbound_messages::InputMessage;
|
||||
use client_core::client::inbound_messages::InputMessageSender;
|
||||
use client_connections::TransmissionLane;
|
||||
use client_core::client::inbound_messages::{InputMessage, InputMessageSender};
|
||||
use futures::channel::mpsc;
|
||||
use futures::task::{Context, Poll};
|
||||
use log::*;
|
||||
@@ -226,17 +226,21 @@ impl SocksClient {
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
|
||||
fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
|
||||
let req = Request::new_connect(self.connection_id, remote_address, self.self_address);
|
||||
let msg = Message::Request(req);
|
||||
|
||||
let input_message = InputMessage::new_fresh(self.service_provider, msg.into_bytes(), false);
|
||||
let input_message = InputMessage::new_fresh(
|
||||
self.service_provider,
|
||||
msg.into_bytes(),
|
||||
false,
|
||||
TransmissionLane::ConnectionId(self.connection_id),
|
||||
);
|
||||
self.input_sender.unbounded_send(input_message).unwrap();
|
||||
}
|
||||
|
||||
async fn run_proxy(&mut self, conn_receiver: ConnectionReceiver, remote_proxy_target: String) {
|
||||
self.send_connect_to_mixnet(remote_proxy_target.clone())
|
||||
.await;
|
||||
self.send_connect_to_mixnet(remote_proxy_target.clone());
|
||||
|
||||
let stream = self.stream.run_proxy();
|
||||
let local_stream_remote = stream
|
||||
@@ -259,7 +263,8 @@ impl SocksClient {
|
||||
.run(move |conn_id, read_data, socket_closed| {
|
||||
let provider_request = Request::new_send(conn_id, read_data, socket_closed);
|
||||
let provider_message = Message::Request(provider_request);
|
||||
InputMessage::new_fresh(recipient, provider_message.into_bytes(), false)
|
||||
let lane = TransmissionLane::ConnectionId(conn_id);
|
||||
InputMessage::new_fresh(recipient, provider_message.into_bytes(), false, lane)
|
||||
})
|
||||
.await
|
||||
.into_inner();
|
||||
|
||||
@@ -4,6 +4,7 @@ use super::{
|
||||
mixnet_responses::MixnetResponseListener,
|
||||
types::{ResponseCode, SocksProxyError},
|
||||
};
|
||||
use client_connections::ClosedConnectionSender;
|
||||
use client_core::client::{
|
||||
inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender,
|
||||
};
|
||||
@@ -51,13 +52,14 @@ impl SphinxSocksServer {
|
||||
&mut self,
|
||||
input_sender: InputMessageSender,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
closed_connection_tx: ClosedConnectionSender,
|
||||
) -> Result<(), SocksProxyError> {
|
||||
let listener = TcpListener::bind(self.listening_address).await.unwrap();
|
||||
info!("Serving Connections...");
|
||||
|
||||
// controller for managing all active connections
|
||||
let (mut active_streams_controller, controller_sender) =
|
||||
Controller::new(self.shutdown.clone());
|
||||
Controller::new(closed_connection_tx, self.shutdown.clone());
|
||||
tokio::spawn(async move {
|
||||
active_streams_controller.run().await;
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ url = "2.2"
|
||||
|
||||
# internal
|
||||
client-core = { path = "../client-core", default-features = false, features = ["wasm"] }
|
||||
client-connections = { path = "../../common/client-connections" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
@@ -59,4 +60,4 @@ wasm-opt = true
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 'z'
|
||||
opt-level = 'z'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
clippy:
|
||||
cargo clippy --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
|
||||
test:
|
||||
wasm-pack test --node
|
||||
@@ -0,0 +1,217 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const TS_DEFS: &'static str = r#"
|
||||
export interface BinaryMessage {
|
||||
kind: number,
|
||||
payload: Uint8Array;
|
||||
headers: string,
|
||||
}
|
||||
|
||||
export interface StringMessage {
|
||||
kind: number,
|
||||
payload: string;
|
||||
}
|
||||
"#;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(typescript_type = "BinaryMessage")]
|
||||
pub type IBinaryMessage;
|
||||
#[wasm_bindgen(typescript_type = "StringMessage")]
|
||||
pub type IStringMessage;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct BinaryMessage {
|
||||
pub kind: u8,
|
||||
pub payload: Vec<u8>,
|
||||
pub headers: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct StringMessage {
|
||||
pub kind: u8,
|
||||
pub payload: String,
|
||||
}
|
||||
|
||||
/// Create a new binary message with a user-specified `kind`.
|
||||
#[wasm_bindgen]
|
||||
pub fn create_binary_message(kind: u8, payload: Vec<u8>) -> Vec<u8> {
|
||||
create_binary_message_with_headers(kind, payload, "".to_string())
|
||||
}
|
||||
|
||||
/// Create a new message with a UTF-8 encoded string `payload` and a user-specified `kind`.
|
||||
#[wasm_bindgen]
|
||||
pub fn create_binary_message_from_string(kind: u8, payload: String) -> Vec<u8> {
|
||||
create_binary_message_with_headers(kind, payload.as_bytes().to_vec(), "".to_string())
|
||||
}
|
||||
|
||||
/// Create a new binary message with a user-specified `kind`, and `headers` as a string.
|
||||
#[wasm_bindgen]
|
||||
pub fn create_binary_message_with_headers(kind: u8, payload: Vec<u8>, headers: String) -> Vec<u8> {
|
||||
let headers = headers.as_bytes().to_vec();
|
||||
let size = (headers.len() as u64).to_be_bytes().to_vec();
|
||||
vec![vec![kind], size, headers, payload].concat()
|
||||
}
|
||||
|
||||
/// Parse the `kind` and byte array `payload` from a byte array
|
||||
#[wasm_bindgen]
|
||||
pub async fn parse_binary_message(message: Vec<u8>) -> Result<IBinaryMessage, JsError> {
|
||||
if message.len() < 2 {
|
||||
return Err(JsError::new(
|
||||
"Could not parse message, as less than 2 bytes long",
|
||||
));
|
||||
}
|
||||
|
||||
let (kind, _headers, payload) = parse_binary_payload(&message);
|
||||
|
||||
Ok(serde_wasm_bindgen::to_value(&BinaryMessage {
|
||||
kind,
|
||||
payload: payload.to_vec(),
|
||||
headers: "".to_string(),
|
||||
})
|
||||
.unwrap()
|
||||
.unchecked_into::<IBinaryMessage>())
|
||||
}
|
||||
|
||||
/// Parse the `kind` and byte array `payload` from a byte array with headers
|
||||
#[wasm_bindgen]
|
||||
pub async fn parse_binary_message_with_headers(
|
||||
message: Vec<u8>,
|
||||
) -> Result<IBinaryMessage, JsError> {
|
||||
if message.len() < 2 {
|
||||
return Err(JsError::new(
|
||||
"Could not parse message, as less than 2 bytes long",
|
||||
));
|
||||
}
|
||||
|
||||
let (kind, headers, payload) = parse_binary_payload(&message);
|
||||
|
||||
Ok(serde_wasm_bindgen::to_value(&BinaryMessage {
|
||||
kind,
|
||||
payload: payload.to_vec(),
|
||||
headers,
|
||||
})
|
||||
.unwrap()
|
||||
.unchecked_into::<IBinaryMessage>())
|
||||
}
|
||||
|
||||
/// Parse the `kind` and UTF-8 string `payload` from a byte array with headers
|
||||
#[wasm_bindgen]
|
||||
pub async fn parse_string_message_with_headers(
|
||||
message: Vec<u8>,
|
||||
) -> Result<IStringMessage, JsError> {
|
||||
if message.len() < 2 {
|
||||
return Err(JsError::new(
|
||||
"Could not parse message, as less than 2 bytes long",
|
||||
));
|
||||
}
|
||||
|
||||
let (kind, _headers, payload) = parse_binary_payload(&message);
|
||||
let payload = String::from_utf8_lossy(payload).into_owned();
|
||||
|
||||
Ok(
|
||||
serde_wasm_bindgen::to_value(&StringMessage { kind, payload })
|
||||
.unwrap()
|
||||
.unchecked_into::<IStringMessage>(),
|
||||
)
|
||||
}
|
||||
pub(crate) fn parse_binary_payload(message: &[u8]) -> (u8, String, &[u8]) {
|
||||
// 1st byte is the kind
|
||||
let kind = message[0];
|
||||
|
||||
// then the size as u64 big endian
|
||||
let mut size = [0u8; 8];
|
||||
size.clone_from_slice(&message[1..9]);
|
||||
let size = u64::from_be_bytes(size) as usize;
|
||||
|
||||
// then the headers
|
||||
let headers = String::from_utf8_lossy(&message[9..9 + size]).into_owned();
|
||||
|
||||
// finally the payload
|
||||
let payload = &message[9 + size..];
|
||||
|
||||
(kind, headers, payload)
|
||||
}
|
||||
|
||||
/// Parse the `kind` and UTF-8 string `payload` from a byte array
|
||||
#[wasm_bindgen]
|
||||
pub async fn parse_string_message(message: Vec<u8>) -> Result<IStringMessage, JsError> {
|
||||
if message.len() < 2 {
|
||||
return Err(JsError::new(
|
||||
"Could not parse message, as less than 2 bytes long",
|
||||
));
|
||||
}
|
||||
|
||||
let kind = message[0];
|
||||
let payload = String::from_utf8_lossy(&message[1..]).into_owned();
|
||||
|
||||
Ok(
|
||||
serde_wasm_bindgen::to_value(&StringMessage { kind, payload })
|
||||
.unwrap()
|
||||
.unchecked_into::<IStringMessage>(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{create_binary_message_with_headers, parse_binary_payload};
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_binary_with_headers() {
|
||||
let message_as_bytes = create_binary_message_with_headers(
|
||||
42u8,
|
||||
vec![0u8, 1u8, 2u8],
|
||||
"test headers".to_string(),
|
||||
);
|
||||
|
||||
// calculate header size
|
||||
let headers = "test headers".as_bytes().to_vec();
|
||||
let size = headers.len();
|
||||
|
||||
// the expected size
|
||||
let expected_size = 12;
|
||||
assert_eq!(size, expected_size);
|
||||
|
||||
assert_eq!(message_as_bytes[0], 42u8);
|
||||
assert_eq!(message_as_bytes[1..9], 12u64.to_be_bytes());
|
||||
assert_eq!(
|
||||
message_as_bytes[9 + expected_size..9 + expected_size + 3],
|
||||
vec![0u8, 1u8, 2u8]
|
||||
);
|
||||
|
||||
let res = parse_binary_payload(&message_as_bytes);
|
||||
|
||||
assert_eq!(res.0, 42u8);
|
||||
assert_eq!(res.1, "test headers".to_string());
|
||||
assert_eq!(res.2, vec![0u8, 1u8, 2u8]);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_binary_with_empty_headers() {
|
||||
let message_as_bytes =
|
||||
create_binary_message_with_headers(42u8, vec![0u8, 1u8, 2u8], "".to_string());
|
||||
|
||||
let expected_size = 0;
|
||||
|
||||
assert_eq!(message_as_bytes[0], 42u8);
|
||||
assert_eq!(message_as_bytes[1..9], 0u64.to_be_bytes());
|
||||
assert_eq!(
|
||||
message_as_bytes[9 + expected_size..9 + expected_size + 3],
|
||||
vec![0u8, 1u8, 2u8]
|
||||
);
|
||||
|
||||
let res = parse_binary_payload(&message_as_bytes);
|
||||
|
||||
assert_eq!(res.0, 42u8);
|
||||
assert_eq!(res.1, "".to_string());
|
||||
assert_eq!(res.2, vec![0u8, 1u8, 2u8]);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use self::config::Config;
|
||||
use client_connections::{ClosedConnectionReceiver, TransmissionLane};
|
||||
use client_core::client::{
|
||||
cover_traffic_stream::LoopCoverTrafficStream,
|
||||
inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender},
|
||||
@@ -25,7 +26,7 @@ use nymsphinx::addressing::clients::Recipient;
|
||||
use rand::rngs::OsRng;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use wasm_utils::{console_log, console_warn};
|
||||
use wasm_utils::console_log;
|
||||
|
||||
pub mod config;
|
||||
|
||||
@@ -44,6 +45,7 @@ pub struct NymClient {
|
||||
|
||||
// callbacks
|
||||
on_message: Option<js_sys::Function>,
|
||||
on_binary_message: Option<js_sys::Function>,
|
||||
on_gateway_connect: Option<js_sys::Function>,
|
||||
}
|
||||
|
||||
@@ -55,6 +57,7 @@ impl NymClient {
|
||||
config,
|
||||
key_manager: Self::setup_key_manager(),
|
||||
on_message: None,
|
||||
on_binary_message: None,
|
||||
on_gateway_connect: None,
|
||||
input_tx: None,
|
||||
}
|
||||
@@ -72,8 +75,11 @@ impl NymClient {
|
||||
self.on_message = Some(on_message);
|
||||
}
|
||||
|
||||
pub fn set_on_binary_message(&mut self, on_binary_message: js_sys::Function) {
|
||||
self.on_binary_message = Some(on_binary_message);
|
||||
}
|
||||
|
||||
pub fn set_on_gateway_connect(&mut self, on_connect: js_sys::Function) {
|
||||
console_log!("setting on connect...");
|
||||
self.on_gateway_connect = Some(on_connect)
|
||||
}
|
||||
|
||||
@@ -122,6 +128,7 @@ impl NymClient {
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
input_receiver: InputMessageReceiver,
|
||||
mix_sender: BatchMixMessageSender,
|
||||
closed_connection_rx: ClosedConnectionReceiver,
|
||||
) {
|
||||
let mut controller_config = real_messages_control::Config::new(
|
||||
self.key_manager.ack_key(),
|
||||
@@ -146,6 +153,7 @@ impl NymClient {
|
||||
input_receiver,
|
||||
mix_sender,
|
||||
topology_accessor,
|
||||
closed_connection_rx,
|
||||
)
|
||||
.start();
|
||||
}
|
||||
@@ -266,6 +274,7 @@ impl NymClient {
|
||||
received_buffer_request_sender: ReceivedBufferRequestSender,
|
||||
) {
|
||||
let on_message = self.on_message.take();
|
||||
let on_binary_message = self.on_binary_message.take();
|
||||
|
||||
spawn_local(async move {
|
||||
let (reconstructed_sender, mut reconstructed_receiver) = mpsc::unbounded();
|
||||
@@ -280,8 +289,14 @@ impl NymClient {
|
||||
let this = JsValue::null();
|
||||
|
||||
while let Some(reconstructed) = reconstructed_receiver.next().await {
|
||||
if let Some(ref callback) = on_message {
|
||||
for msg in reconstructed {
|
||||
for msg in reconstructed {
|
||||
if let Some(ref callback_binary) = on_binary_message {
|
||||
let arg1 = serde_wasm_bindgen::to_value(&msg.message).unwrap();
|
||||
callback_binary
|
||||
.call1(&this, &arg1)
|
||||
.expect("on binary message failed!");
|
||||
}
|
||||
if let Some(ref callback) = on_message {
|
||||
if msg.reply_surb.is_some() {
|
||||
console_log!("the received message contained a reply-surb that we do not know how to handle (yet)")
|
||||
}
|
||||
@@ -289,9 +304,6 @@ impl NymClient {
|
||||
let arg1 = serde_wasm_bindgen::to_value(&stringified).unwrap();
|
||||
callback.call1(&this, &arg1).expect("on message failed!");
|
||||
}
|
||||
} else {
|
||||
console_warn!("no on_message callback was specified. the received message content is getting dropped");
|
||||
console_log!("the raw messages: {:?}", reconstructed)
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -318,6 +330,10 @@ impl NymClient {
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
let shared_topology_accessor = TopologyAccessor::new();
|
||||
|
||||
// Channel that the real traffix controller can listed to for closing connections.
|
||||
// Currently unused in the wasm client.
|
||||
let (_closed_connection_tx, closed_connection_rx) = mpsc::unbounded();
|
||||
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone())
|
||||
@@ -342,6 +358,7 @@ impl NymClient {
|
||||
ack_receiver,
|
||||
input_receiver,
|
||||
sphinx_message_sender.clone(),
|
||||
closed_connection_rx,
|
||||
);
|
||||
|
||||
if !self.config.debug.disable_loop_cover_traffic_stream {
|
||||
@@ -367,8 +384,9 @@ impl NymClient {
|
||||
console_log!("Sending {} bytes to {}", message.len(), recipient);
|
||||
|
||||
let recipient = Recipient::try_from_base58_string(recipient).unwrap();
|
||||
let lane = TransmissionLane::General;
|
||||
|
||||
let input_msg = InputMessage::new_fresh(recipient, message, false);
|
||||
let input_msg = InputMessage::new_fresh(recipient, message, false, lane);
|
||||
|
||||
self.input_tx
|
||||
.as_ref()
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod binary_message_helper;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod client;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "client-connections"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::channel::mpsc;
|
||||
|
||||
pub type ConnectionId = u64;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum TransmissionLane {
|
||||
General,
|
||||
Reply,
|
||||
Retransmission,
|
||||
Control,
|
||||
ConnectionId(ConnectionId),
|
||||
}
|
||||
|
||||
/// Announce connections that are closed, for whoever is interested.
|
||||
/// One usecase is that the network-requester and socks5-client wants to know about this, so that
|
||||
/// they can forward this to the `OutQueueControl` (via `ClientRequest` for the network-requester)
|
||||
pub type ClosedConnectionSender = mpsc::UnboundedSender<ConnectionId>;
|
||||
pub type ClosedConnectionReceiver = mpsc::UnboundedReceiver<ConnectionId>;
|
||||
@@ -13,7 +13,7 @@ use validator_client::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
pub struct SignatureOutputJson {
|
||||
pub account_id: String,
|
||||
pub public_key: PublicKey,
|
||||
pub signature: String,
|
||||
pub signature_as_hex: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -46,7 +46,7 @@ pub fn sign(args: Args, prefix: &str, mnemonic: Option<bip39::Mnemonic>) {
|
||||
let output = SignatureOutputJson {
|
||||
account_id: account.address().to_string(),
|
||||
public_key: account.public_key(),
|
||||
signature: signature.to_string(),
|
||||
signature_as_hex: signature.to_string(),
|
||||
};
|
||||
println!("{}", json!(output));
|
||||
}
|
||||
|
||||
@@ -413,4 +413,6 @@ pub enum QueryMsg {
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {}
|
||||
pub struct MigrateMsg {
|
||||
pub vesting_contract_address: Option<String>,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ mixnet-contract-common = { path = "../mixnet-contract" }
|
||||
contracts-common = { path = "../contracts-common" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
log = "0.4"
|
||||
ts-rs = {version = "6.1.2", optional = true}
|
||||
|
||||
[features]
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
use contracts_common::Percent;
|
||||
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
|
||||
use log::warn;
|
||||
use mixnet_contract_common::MixId;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -62,23 +61,11 @@ impl FromStr for PledgeCap {
|
||||
fn from_str(cap: &str) -> Result<Self, Self::Err> {
|
||||
let cap = cap.replace('_', "").replace(',', ".");
|
||||
match Percent::from_str(&cap) {
|
||||
Ok(p) => {
|
||||
if p.is_zero() {
|
||||
warn!("Pledge cap set to 0%, are you sure this is right?")
|
||||
}
|
||||
Ok(PledgeCap::Percent(p))
|
||||
}
|
||||
Err(_) => {
|
||||
match cap.parse::<u128>() {
|
||||
Ok(i) => {
|
||||
if i < 100_000_000_000 {
|
||||
warn!("PledgeCap set to less then 100_000 NYM, are you sure this is right?");
|
||||
}
|
||||
Ok(PledgeCap::Absolute(Uint128::from(i)))
|
||||
}
|
||||
Err(_e) => Err(format!("Could not parse {} as Percent or Uint128", cap)),
|
||||
}
|
||||
}
|
||||
Ok(p) => Ok(PledgeCap::Percent(p)),
|
||||
Err(_) => match cap.parse::<u128>() {
|
||||
Ok(i) => Ok(PledgeCap::Absolute(Uint128::from(i))),
|
||||
Err(_e) => Err(format!("Could not parse {} as Percent or Uint128", cap)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
|
||||
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
|
||||
|
||||
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
|
||||
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
|
||||
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr";
|
||||
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
|
||||
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
|
||||
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
|
||||
|
||||
@@ -14,8 +14,11 @@ tokio-util = { version = "0.7.3", features = [ "io" ] } # reason for getting thi
|
||||
# In the long run, the dependency should probably get removed in favour of pure-tokio implementation, but for time being it's fine.
|
||||
futures = "0.3"
|
||||
log = "0.4"
|
||||
socks5-requests = { path = "../requests" }
|
||||
|
||||
# internal
|
||||
client-connections = { path = "../../client-connections" }
|
||||
ordered-buffer = { path = "../ordered-buffer" }
|
||||
socks5-requests = { path = "../requests" }
|
||||
task = { path = "../../task" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use client_connections::ClosedConnectionSender;
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
@@ -73,6 +74,9 @@ pub struct Controller {
|
||||
// to avoid memory issues
|
||||
recently_closed: HashSet<ConnectionId>,
|
||||
|
||||
// Broadcast closed connections
|
||||
closed_connection_tx: ClosedConnectionSender,
|
||||
|
||||
// TODO: this can potentially be abused to ddos and kill provider. Not sure at this point
|
||||
// how to handle it more gracefully
|
||||
|
||||
@@ -84,13 +88,17 @@ pub struct Controller {
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub fn new(shutdown: ShutdownListener) -> (Self, ControllerSender) {
|
||||
pub fn new(
|
||||
closed_connection_tx: ClosedConnectionSender,
|
||||
shutdown: ShutdownListener,
|
||||
) -> (Self, ControllerSender) {
|
||||
let (sender, receiver) = mpsc::unbounded();
|
||||
(
|
||||
Controller {
|
||||
active_connections: HashMap::new(),
|
||||
receiver,
|
||||
recently_closed: HashSet::new(),
|
||||
closed_connection_tx,
|
||||
pending_messages: HashMap::new(),
|
||||
shutdown,
|
||||
},
|
||||
@@ -127,6 +135,9 @@ impl Controller {
|
||||
)
|
||||
}
|
||||
self.recently_closed.insert(conn_id);
|
||||
|
||||
// Announce closed connections, currently used by the `OutQueueControl`.
|
||||
self.closed_connection_tx.unbounded_send(conn_id).unwrap();
|
||||
}
|
||||
|
||||
fn send_to_connection(&mut self, conn_id: ConnectionId, payload: Vec<u8>, is_closed: bool) {
|
||||
@@ -172,11 +183,15 @@ impl Controller {
|
||||
pending.push((payload, is_closed));
|
||||
} else if !is_closed {
|
||||
error!(
|
||||
"Tried to write to closed connection ({} bytes were 'lost)",
|
||||
"Tried to write to closed connection {} ({} bytes were 'lost)",
|
||||
conn_id,
|
||||
payload.len()
|
||||
);
|
||||
} else {
|
||||
debug!("Tried to write to closed connection, but remote is already closed")
|
||||
debug!(
|
||||
"Tried to write to closed connection {}, but remote is already closed",
|
||||
conn_id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,10 @@ where
|
||||
|
||||
// if we're sending through the mixnet increase the sequence number...
|
||||
let ordered_msg = message_sender.wrap_message(read_data.to_vec()).into_bytes();
|
||||
log::trace!(
|
||||
"pushing data down the input sender: size: {}",
|
||||
ordered_msg.len()
|
||||
);
|
||||
mix_sender
|
||||
.unbounded_send(adapter_fn(connection_id, ordered_msg, is_finished))
|
||||
.unwrap();
|
||||
|
||||
+36
-4
@@ -1,8 +1,43 @@
|
||||
## Unreleased
|
||||
|
||||
## Added
|
||||
|
||||
- Added migration code to the mixnet contract to allow updating stored vesting contract address to make it easier to deploy any future environments ([#1759],[#1769])
|
||||
|
||||
[#1759]: https://github.com/nymtech/nym/pull/1759
|
||||
[#1769]: https://github.com/nymtech/nym/pull/1769
|
||||
|
||||
## [nym-contracts-v1.1.0](https://github.com/nymtech/nym/tree/nym-contracts-v1.1.0) (2022-11-09)
|
||||
|
||||
### Changed
|
||||
- mixnet-contract: rework of rewarding ([#1472]), which includes, but is not limited to:
|
||||
- internal reward accounting was modified to be similar to the ideas presented in Cosmos' F1 paper, which results in throughput gains and no storage or gas cost bloat over time,
|
||||
- introduced internal queues for pending epoch and interval events that only get resolved once relevant epoch/interval rolls over
|
||||
- the contract no longer stores any historical information regarding past epochs/parameters/stake state for the purposes of rewarding
|
||||
- a lot of queries got renamed to keep naming more consistent,
|
||||
- introduced new utility-based queries such as a query for reward estimation for the current epoch,
|
||||
- mixnodes are now identified by a monotonously increasing `mix_id`
|
||||
- bonding now results in getting fresh `mix_id` and thus if given node decides to unbond and rebond, it will lose all its delegations,
|
||||
- mixnode operators are now allowed to set their operating costs as opposed to having fixed value of 40nym/interval
|
||||
- rewarding parameters are now correctly updated at an **interval** end
|
||||
- rewarding parameters now include a staking supply scale factor attribute (beta in the tokenomics paper)
|
||||
- node performance can now be more granular with internal `Decimal` representation as opposed to an `u8`
|
||||
- node profit margin can now be more granular with internal `Decimal` representation as opposed to an `u8`
|
||||
- mixnode operators are now allowed to change their configuration options, such as port information, without having to unbond
|
||||
- mixnode unbonding is no longer instantaneous, instead it happens once an epoch rolls over
|
||||
- it is now possible to query for operator and node history to see how often (and with what parameters) they rebonded
|
||||
- other minor bugfixes and changes
|
||||
- ...
|
||||
- new exciting bugs to find and squash
|
||||
|
||||
- vesting-contract: optional locked token pledge cap per account ([#1687]), defaults to 10%
|
||||
- vesting-contract: updated internal delegation storage due to mixnet contract revamp ([#1472])
|
||||
|
||||
### Added
|
||||
- vesting-contract: added query for obtaining contract build information ([#1726])
|
||||
|
||||
[#1472]: https://github.com/nymtech/nym/pull/1472
|
||||
[#1687]: https://github.com/nymtech/nym/pull/1687
|
||||
[#1726]: https://github.com/nymtech/nym/pull/1726
|
||||
|
||||
|
||||
@@ -11,12 +46,10 @@
|
||||
### Added
|
||||
|
||||
- vesting-contract: added queries for delegation timestamps and paged query for all vesting delegations in the contract ([#1569])
|
||||
- all binaries: added shell completion and [Fig](fig.io) spec generation ([#1638])
|
||||
|
||||
### Changed
|
||||
|
||||
- mixnet-contract: compounding delegator rewards now happens instantaneously as opposed to having to wait for the current epoch to finish ([#1571])
|
||||
- network-requester: updated CLI to use `clap` macros ([#1638])
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -26,9 +59,8 @@
|
||||
|
||||
[#1544]: https://github.com/nymtech/nym/pull/1544
|
||||
[#1569]: https://github.com/nymtech/nym/pull/1569
|
||||
[#1569]: https://github.com/nymtech/nym/pull/1571
|
||||
[#1571]: https://github.com/nymtech/nym/pull/1571
|
||||
[#1613]: https://github.com/nymtech/nym/pull/1613
|
||||
[#1638]: https://github.com/nymtech/nym/pull/1638
|
||||
|
||||
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
|
||||
|
||||
|
||||
Generated
+3
-4
@@ -287,9 +287,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -930,7 +930,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "mixnet-contract"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-schema",
|
||||
@@ -1625,7 +1625,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mixnet-contract"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -467,10 +467,20 @@ pub fn query(
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(
|
||||
_deps: DepsMut<'_>,
|
||||
deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
_msg: MigrateMsg,
|
||||
msg: MigrateMsg,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
|
||||
// and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh
|
||||
// environment, one of the contracts will HAVE TO go through a migration
|
||||
if let Some(vesting_contract_address) = msg.vesting_contract_address {
|
||||
let mut current_state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?;
|
||||
current_state.vesting_contract_address =
|
||||
deps.api.addr_validate(&vesting_contract_address)?;
|
||||
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, ¤t_state)?;
|
||||
}
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -420,9 +420,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -7,6 +7,6 @@ use clap::Parser;
|
||||
#[clap(author = "Nymtech", version, about)]
|
||||
pub(crate) struct Cli {
|
||||
/// Path pointing to an env file that configures the explorer api.
|
||||
#[clap(long)]
|
||||
#[clap(short, long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
}
|
||||
|
||||
@@ -95,12 +95,12 @@ impl GeoLocateTask {
|
||||
.await;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"❌ Oh no! Location for {} failed. Error: {:#?}",
|
||||
cache_item.mix_node().host,
|
||||
e
|
||||
);
|
||||
Err(_e) => {
|
||||
// warn!(
|
||||
// "❌ Oh no! Location for {} failed. Error: {:#?}",
|
||||
// cache_item.mix_node().host,
|
||||
// e
|
||||
// );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ pub(crate) async fn retrieve_mixnode_econ_stats(
|
||||
|
||||
Some(EconomicDynamicsStats {
|
||||
stake_saturation: best_effort_small_dec_to_f64(stake_saturation.saturation) as f32,
|
||||
uncapped_saturation: best_effort_small_dec_to_f64(stake_saturation.uncapped_saturation)
|
||||
as f32,
|
||||
active_set_inclusion_probability: inclusion_probability.in_active,
|
||||
reserve_set_inclusion_probability: inclusion_probability.in_reserve,
|
||||
// drop precision for compatibility sake
|
||||
|
||||
@@ -34,6 +34,7 @@ pub(crate) struct PrettyDetailedMixNodeBond {
|
||||
pub layer: Layer,
|
||||
pub mix_node: MixNode,
|
||||
pub stake_saturation: f32,
|
||||
pub uncapped_saturation: f32,
|
||||
pub avg_uptime: u8,
|
||||
pub estimated_operator_apy: f64,
|
||||
pub estimated_delegators_apy: f64,
|
||||
@@ -153,6 +154,7 @@ pub(crate) struct NodeStats {
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub(crate) struct EconomicDynamicsStats {
|
||||
pub(crate) stake_saturation: f32,
|
||||
pub(crate) uncapped_saturation: f32,
|
||||
|
||||
pub(crate) active_set_inclusion_probability: SelectionChance,
|
||||
pub(crate) reserve_set_inclusion_probability: SelectionChance,
|
||||
|
||||
@@ -151,6 +151,8 @@ impl ThreadsafeMixNodesCache {
|
||||
mix_node: node.mixnode_details.bond_information.mix_node.clone(),
|
||||
avg_uptime: node.performance.round_to_integer(),
|
||||
stake_saturation: best_effort_small_dec_to_f64(node.stake_saturation) as f32,
|
||||
uncapped_saturation: best_effort_small_dec_to_f64(node.uncapped_stake_saturation)
|
||||
as f32,
|
||||
estimated_operator_apy: best_effort_small_dec_to_f64(node.estimated_operator_apy),
|
||||
estimated_delegators_apy: best_effort_small_dec_to_f64(node.estimated_delegators_apy),
|
||||
operating_cost: rewarding_info.cost_params.interval_operating_cost.clone(),
|
||||
|
||||
@@ -6,14 +6,16 @@ export const EconomicsInfoColumns: ColumnsType[] = [
|
||||
title: 'Estimated Total Reward',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo: 'Estimated reward per epoch for this profit margin if your node is selected in the active set.',
|
||||
tooltipInfo:
|
||||
'Estimated node reward (total for the operator and delegators) in the current epoch. There are roughly 24 epochs in a day.',
|
||||
},
|
||||
{
|
||||
field: 'estimatedOperatorReward',
|
||||
title: 'Estimated Operator Reward',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo: 'Estimated reward per epoch for this profit margin if your node is selected in the active set.',
|
||||
tooltipInfo:
|
||||
"Estimated operator's reward (including PM and Operating Cost) in the current epoch. There are roughly 24 epochs in a day.",
|
||||
},
|
||||
{
|
||||
field: 'selectionChance',
|
||||
@@ -29,7 +31,7 @@ export const EconomicsInfoColumns: ColumnsType[] = [
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo:
|
||||
'Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 1 million NYM, computed as S/K where S is total amount of tokens available to stakeholders and K is the number of nodes in the reward set.',
|
||||
'Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 750k NYM, computed as S/K where S is total amount of tokens available to stakeholders and K is the number of nodes in the reward set.',
|
||||
},
|
||||
{
|
||||
field: 'profitMargin',
|
||||
@@ -37,7 +39,7 @@ export const EconomicsInfoColumns: ColumnsType[] = [
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo:
|
||||
'Percentage of the delegates rewards that the operator takes as fee before rewards are distributed to the delegates.',
|
||||
'Percentage of the delegators rewards that the operator takes as fee before rewards are distributed to the delegators.',
|
||||
},
|
||||
{
|
||||
field: 'operatingCost',
|
||||
|
||||
@@ -4,23 +4,8 @@ import { ApiState, MixNodeEconomicDynamicsStatsResponse } from '../../../typeDef
|
||||
import { EconomicsInfoRowWithIndex } from './types';
|
||||
import { toPercentIntegerString } from '../../../utils';
|
||||
|
||||
const selectionChance = (economicDynamicsStats: ApiState<MixNodeEconomicDynamicsStatsResponse> | undefined) => {
|
||||
const inclusionProbability = economicDynamicsStats?.data?.active_set_inclusion_probability;
|
||||
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
|
||||
switch (inclusionProbability) {
|
||||
case 'VeryLow':
|
||||
return 'Very Low';
|
||||
case 'VeryHigh':
|
||||
return 'Very High';
|
||||
case 'High':
|
||||
case 'Good':
|
||||
case 'Low':
|
||||
case 'Moderate':
|
||||
return inclusionProbability;
|
||||
default:
|
||||
return '-';
|
||||
}
|
||||
};
|
||||
const selectionChance = (economicDynamicsStats: ApiState<MixNodeEconomicDynamicsStatsResponse> | undefined) =>
|
||||
economicDynamicsStats?.data?.active_set_inclusion_probability || '-';
|
||||
|
||||
export const EconomicsInfoRows = (): EconomicsInfoRowWithIndex => {
|
||||
const { economicDynamicsStats, mixNode } = useMixnodeContext();
|
||||
@@ -29,7 +14,7 @@ export const EconomicsInfoRows = (): EconomicsInfoRowWithIndex => {
|
||||
currencyToString((economicDynamicsStats?.data?.estimated_total_node_reward || '').toString()) || '-';
|
||||
const estimatedOperatorRewards =
|
||||
currencyToString((economicDynamicsStats?.data?.estimated_operator_reward || '').toString()) || '-';
|
||||
const stakeSaturation = economicDynamicsStats?.data?.stake_saturation || '-';
|
||||
const stakeSaturation = economicDynamicsStats?.data?.uncapped_saturation || '-';
|
||||
const profitMargin = mixNode?.data?.profit_margin_percent
|
||||
? toPercentIntegerString(mixNode?.data?.profit_margin_percent)
|
||||
: '-';
|
||||
|
||||
@@ -31,7 +31,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
|
||||
const totalBond = pledge + delegations;
|
||||
const selfPercentage = ((pledge * 100) / totalBond).toFixed(2);
|
||||
const profitPercentage = toPercentIntegerString(item.profit_margin_percent) || 0;
|
||||
const stakeSaturation = typeof item.stake_saturation === 'number' ? item.stake_saturation * 100 : 0;
|
||||
const uncappedSaturation = typeof item.uncapped_saturation === 'number' ? item.uncapped_saturation * 100 : 0;
|
||||
|
||||
return {
|
||||
mix_id: item.mix_id,
|
||||
@@ -47,7 +47,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
|
||||
layer: item?.layer || '',
|
||||
profit_percentage: `${profitPercentage}%`,
|
||||
avg_uptime: `${item.avg_uptime}%` || '-',
|
||||
stake_saturation: stakeSaturation,
|
||||
operating_cost: `${unymToNym(item.operating_cost.amount, 6)} NYM`,
|
||||
stake_saturation: uncappedSaturation,
|
||||
operating_cost: `${unymToNym(item.operating_cost?.amount, 6)} NYM`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import { Menu } from '@mui/icons-material';
|
||||
import { NymLogo } from '@nymproject/react/logo/NymLogo';
|
||||
import { MaintenanceBanner } from '@nymproject/react/banners/MaintenanceBanner';
|
||||
import { useMainContext } from '../context/main';
|
||||
import { MobileDrawerClose } from '../icons/MobileDrawerClose';
|
||||
import { Footer } from './Footer';
|
||||
@@ -32,6 +33,8 @@ export const MobileNav: React.FC<{ children: React.ReactNode }> = ({ children }:
|
||||
const theme = useTheme();
|
||||
const { navState, updateNavState } = useMainContext();
|
||||
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
||||
// Set maintenance banner to false by default to don't display it
|
||||
const [openMaintenance, setOpenMaintenance] = React.useState(true);
|
||||
|
||||
const toggleDrawer = () => {
|
||||
setDrawerOpen(!drawerOpen);
|
||||
@@ -51,8 +54,10 @@ export const MobileNav: React.FC<{ children: React.ReactNode }> = ({ children }:
|
||||
<AppBar
|
||||
sx={{
|
||||
background: theme.palette.nym.networkExplorer.topNav.appBar,
|
||||
borderRadius: 0,
|
||||
}}
|
||||
>
|
||||
<MaintenanceBanner open={openMaintenance} onClick={() => setOpenMaintenance(false)} />
|
||||
<Toolbar
|
||||
disableGutters
|
||||
sx={{
|
||||
|
||||
@@ -15,6 +15,7 @@ import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import { NymLogo } from '@nymproject/react/logo/NymLogo';
|
||||
import { MaintenanceBanner } from '@nymproject/react/banners/MaintenanceBanner';
|
||||
import { NYM_WEBSITE } from '../api/constants';
|
||||
import { useMainContext } from '../context/main';
|
||||
import { MobileDrawerClose } from '../icons/MobileDrawerClose';
|
||||
@@ -24,6 +25,7 @@ import { DarkLightSwitchDesktop } from './Switch';
|
||||
import { NavOptionType } from '../context/nav';
|
||||
|
||||
const drawerWidth = 255;
|
||||
const bannerHeight = 80;
|
||||
|
||||
const openedMixin = (theme: Theme): CSSObject => ({
|
||||
width: drawerWidth,
|
||||
@@ -232,6 +234,8 @@ export const Nav: React.FC = ({ children }) => {
|
||||
const { updateNavState, navState } = useMainContext();
|
||||
const [drawerIsOpen, setDrawerToOpen] = React.useState(false);
|
||||
const [fixedOpen, setFixedOpen] = React.useState(false);
|
||||
// Set maintenance banner to false by default to don't display it
|
||||
const [openMaintenance, setOpenMaintenance] = React.useState(true);
|
||||
const theme = useTheme();
|
||||
|
||||
const setToActive = (id: number) => {
|
||||
@@ -268,6 +272,7 @@ export const Nav: React.FC = ({ children }) => {
|
||||
borderRadius: 0,
|
||||
}}
|
||||
>
|
||||
<MaintenanceBanner open={openMaintenance} onClick={() => setOpenMaintenance(false)} height={bannerHeight} />
|
||||
<Toolbar
|
||||
disableGutters
|
||||
sx={{
|
||||
@@ -332,6 +337,7 @@ export const Nav: React.FC = ({ children }) => {
|
||||
style: {
|
||||
background: theme.palette.nym.networkExplorer.nav.background,
|
||||
borderRadius: 0,
|
||||
top: openMaintenance ? bannerHeight : 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Link as RRDLink } from 'react-router-dom';
|
||||
import { Box, Button, Card, Grid, Typography, Link as MuiLink } from '@mui/material';
|
||||
import { Box, Button, Card, Grid, Link as MuiLink } from '@mui/material';
|
||||
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
|
||||
import { SelectChangeEvent } from '@mui/material/Select';
|
||||
import { useMainContext } from '../../context/main';
|
||||
@@ -12,6 +12,8 @@ import { Title } from '../../components/Title';
|
||||
import { cellStyles, UniversalDataGrid } from '../../components/Universal-DataGrid';
|
||||
import { currencyToString } from '../../utils/currency';
|
||||
import { Tooltip } from '../../components/Tooltip';
|
||||
import { BIG_DIPPER } from '../../api/constants';
|
||||
import { splice } from '../../utils';
|
||||
|
||||
export const PageGateways: React.FC = () => {
|
||||
const { gateways } = useMainContext();
|
||||
@@ -69,9 +71,14 @@ export const PageGateways: React.FC = () => {
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<Typography sx={cellStyles} data-testid="pledge-amount">
|
||||
<MuiLink
|
||||
sx={{ ...cellStyles }}
|
||||
component={RRDLink}
|
||||
to={`/network-components/gateway/${params.row.identityKey}`}
|
||||
data-testid="pledge-amount"
|
||||
>
|
||||
{currencyToString(params.value)}
|
||||
</Typography>
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -81,9 +88,14 @@ export const PageGateways: React.FC = () => {
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<Typography sx={cellStyles} data-testid="host">
|
||||
<MuiLink
|
||||
sx={{ ...cellStyles }}
|
||||
component={RRDLink}
|
||||
to={`/network-components/gateway/${params.row.identityKey}`}
|
||||
data-testid="host"
|
||||
>
|
||||
{params.value}
|
||||
</Typography>
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -120,9 +132,14 @@ export const PageGateways: React.FC = () => {
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<Typography sx={cellStyles} data-testid="owner">
|
||||
{params.value}
|
||||
</Typography>
|
||||
<MuiLink
|
||||
sx={{ ...cellStyles }}
|
||||
href={`${BIG_DIPPER}/account/${params.value}`}
|
||||
target="_blank"
|
||||
data-testid="owner"
|
||||
>
|
||||
{splice(7, 29, params.value)}
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -35,7 +35,7 @@ const columns: ColumnsType[] = [
|
||||
},
|
||||
{
|
||||
field: 'self_percentage',
|
||||
title: 'Self %',
|
||||
title: 'Bond %',
|
||||
headerAlign: 'left',
|
||||
width: 99,
|
||||
},
|
||||
|
||||
@@ -149,7 +149,7 @@ export const PageMixnodes: React.FC = () => {
|
||||
renderHeader: () => (
|
||||
<CustomColumnHeading
|
||||
headingTitle="Stake Saturation"
|
||||
tooltipInfo="Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 1 million NYM, computed as S/K where S is total amount of tokens available to stakeholders and K is the number of nodes in the reward set."
|
||||
tooltipInfo="Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 750k NYM, computed as S/K where S is total amount of tokens available to stakeholders and K is the number of nodes in the reward set."
|
||||
/>
|
||||
),
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
@@ -192,7 +192,7 @@ export const PageMixnodes: React.FC = () => {
|
||||
renderHeader: () => (
|
||||
<CustomColumnHeading
|
||||
headingTitle="Profit Margin"
|
||||
tooltipInfo="Percentage of the delegates rewards that the operator takes as fee before rewards are distributed to the delegates."
|
||||
tooltipInfo="Percentage of the delegators rewards that the operator takes as fee before rewards are distributed to the delegators."
|
||||
/>
|
||||
),
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
@@ -210,10 +210,10 @@ export const PageMixnodes: React.FC = () => {
|
||||
},
|
||||
{
|
||||
field: 'operating_cost',
|
||||
headerName: 'Operating cost',
|
||||
headerName: 'Operating Cost',
|
||||
renderHeader: () => (
|
||||
<CustomColumnHeading
|
||||
headingTitle="Operating cost"
|
||||
headingTitle="Operating Cost"
|
||||
tooltipInfo="Monthly operational cost of running this node. This cost is set by the operator and it influences how the rewards are split between the operator and delegators."
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -85,6 +85,7 @@ export interface MixNodeResponseItem {
|
||||
mix_node: MixNode;
|
||||
avg_uptime: number;
|
||||
stake_saturation: number;
|
||||
uncapped_saturation: number;
|
||||
operating_cost: Amount;
|
||||
profit_margin_percent: string;
|
||||
}
|
||||
@@ -214,8 +215,9 @@ export type UptimeStoryResponse = {
|
||||
|
||||
export type MixNodeEconomicDynamicsStatsResponse = {
|
||||
stake_saturation: number;
|
||||
uncapped_saturation: number;
|
||||
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
|
||||
active_set_inclusion_probability: 'High' | 'Good' | 'Low' | 'VeryLow' | 'Moderate' | 'VeryHigh';
|
||||
active_set_inclusion_probability: 'High' | 'Good' | 'Low';
|
||||
reserve_set_inclusion_probability: 'High' | 'Good' | 'Low';
|
||||
estimated_total_node_reward: number;
|
||||
estimated_operator_reward: number;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ fn long_version_static() -> &'static str {
|
||||
#[clap(author = "Nymtech", version, about, long_version = long_version_static())]
|
||||
struct Cli {
|
||||
/// Path pointing to an env file that configures the gateway.
|
||||
#[clap(long)]
|
||||
#[clap(short, long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
#[clap(subcommand)]
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ fn long_version_static() -> &'static str {
|
||||
#[clap(author = "Nymtech", version, about, long_version = long_version_static())]
|
||||
struct Cli {
|
||||
/// Path pointing to an env file that configures the mixnode.
|
||||
#[clap(long)]
|
||||
#[clap(short, long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
#[clap(subcommand)]
|
||||
|
||||
Generated
+2
-2
@@ -285,9 +285,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.2"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
## [nym-connect-v1.1.0](https://github.com/nymtech/nym/tree/nym-connect-v1.1.0) (2022-11-09)
|
||||
|
||||
- nym-connect: rework of rewarding changes the directory data structures that describe the mixnet topology ([#1472])
|
||||
- clients: add testing-only support for two more extended packet sizes (8kb and 16kb).
|
||||
- native-client/socks5-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
|
||||
- native-client/socks5-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
|
||||
- native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
|
||||
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611])
|
||||
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
|
||||
|
||||
[#1472]: https://github.com/nymtech/nym/pull/1472
|
||||
[#1558]: https://github.com/nymtech/nym/pull/1558
|
||||
[#1611]: https://github.com/nymtech/nym/pull/1611
|
||||
[#1664]: https://github.com/nymtech/nym/pull/1664
|
||||
[#1666]: https://github.com/nymtech/nym/pull/1666
|
||||
[#1671]: https://github.com/nymtech/nym/pull/1671
|
||||
|
||||
## [nym-connect-v1.0.2](https://github.com/nymtech/nym/tree/nym-connect-v1.0.2) (2022-08-18)
|
||||
|
||||
### Changed
|
||||
|
||||
Generated
+16
-7
@@ -595,9 +595,17 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.0.1"
|
||||
name = "client-connections"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"client-connections",
|
||||
"config",
|
||||
"crypto",
|
||||
"dirs",
|
||||
@@ -3214,7 +3222,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-connect"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"bip39",
|
||||
"client-core",
|
||||
@@ -3247,9 +3255,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"client-connections",
|
||||
"client-core",
|
||||
"completions",
|
||||
"config",
|
||||
@@ -4034,6 +4043,7 @@ name = "proxy-helpers"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"client-connections",
|
||||
"futures",
|
||||
"log",
|
||||
"ordered-buffer",
|
||||
@@ -5648,9 +5658,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.1"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
|
||||
checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
|
||||
|
||||
[[package]]
|
||||
name = "thin-slice"
|
||||
@@ -6189,7 +6199,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"eslint-plugin-react": "^7.29.2",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-storybook": "^0.5.12",
|
||||
"favicons": "^6.2.2",
|
||||
"favicons": "^7.0.2",
|
||||
"favicons-webpack-plugin": "^5.0.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^7.2.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-connect"
|
||||
version = "1.0.2"
|
||||
version = "1.1.0"
|
||||
description = "nym-connect"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-connect",
|
||||
"version": "1.0.2"
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { NymWordmark } from '@nymproject/react/logo/NymWordmark';
|
||||
export const AppWindowFrame: React.FC = ({ children }) => (
|
||||
<Box
|
||||
sx={{
|
||||
background: '#121726',
|
||||
background: (t) => t.palette.background.default,
|
||||
borderRadius: '12px',
|
||||
padding: '12px 16px',
|
||||
display: 'grid',
|
||||
|
||||
@@ -2,10 +2,10 @@ import React from 'react';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
|
||||
const getBusyFillColor = (color: string): string => {
|
||||
if (color === '#60D6EF') {
|
||||
if (color === '#F4B02D') {
|
||||
return '#21D072';
|
||||
}
|
||||
return '#60D6EF';
|
||||
return '#F4B02D';
|
||||
};
|
||||
|
||||
const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => {
|
||||
@@ -21,10 +21,10 @@ const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isErro
|
||||
if (hover) {
|
||||
return '#21D072';
|
||||
}
|
||||
return '#60D6EF';
|
||||
return '#F4B02D';
|
||||
case ConnectionStatusKind.connecting:
|
||||
case ConnectionStatusKind.disconnecting:
|
||||
return '#60D6EF';
|
||||
return '#F4B02D';
|
||||
default:
|
||||
// connected
|
||||
if (hover) {
|
||||
|
||||
@@ -2,8 +2,10 @@ import React, { useEffect, useMemo } from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import ArrowDropDownCircleIcon from '@mui/icons-material/ArrowDropDownCircle';
|
||||
import { Box, CircularProgress, Stack, Tooltip, Typography } from '@mui/material';
|
||||
import KeyboardArrowDownRoundedIcon from '@mui/icons-material/KeyboardArrowDownRounded';
|
||||
import KeyboardArrowUpRoundedIcon from '@mui/icons-material/KeyboardArrowUpRounded';
|
||||
import { Box, CircularProgress, Stack, Tooltip, Typography, ListItemIcon } from '@mui/material';
|
||||
import Check from '@mui/icons-material/Check';
|
||||
import { ServiceProvider, Service, Services } from '../types/directory';
|
||||
|
||||
type ServiceWithRandomSp = {
|
||||
@@ -62,7 +64,7 @@ export const ServiceProviderSelector: React.FC<{
|
||||
Loading services...
|
||||
</Typography>
|
||||
<IconButton id="service-provider-button" disabled>
|
||||
<ArrowDropDownCircleIcon />
|
||||
{open ? <KeyboardArrowUpRoundedIcon /> : <KeyboardArrowDownRoundedIcon />}
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
@@ -80,14 +82,14 @@ export const ServiceProviderSelector: React.FC<{
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ mt: 3 }}>
|
||||
<Typography
|
||||
ref={textEl}
|
||||
fontSize={14}
|
||||
fontWeight={700}
|
||||
color={(theme) => (serviceProvider ? undefined : theme.palette.primary.main)}
|
||||
>
|
||||
{service ? service.description : 'Select a service'}
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
sx={{ mt: 3, borderBottom: (theme) => `1px solid ${theme.palette.info.main}` }}
|
||||
>
|
||||
<Typography ref={textEl} fontSize={14} fontWeight={700} color={(theme) => theme.palette.info.main}>
|
||||
{!service ? 'Select a service' : service.description}
|
||||
</Typography>
|
||||
<IconButton
|
||||
id="service-provider-button"
|
||||
@@ -95,8 +97,11 @@ export const ServiceProviderSelector: React.FC<{
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
onClick={handleClick}
|
||||
color="info"
|
||||
size="small"
|
||||
sx={{ padding: 0 }}
|
||||
>
|
||||
<ArrowDropDownCircleIcon />
|
||||
{open ? <KeyboardArrowUpRoundedIcon /> : <KeyboardArrowDownRoundedIcon />}
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Menu
|
||||
@@ -112,6 +117,11 @@ export const ServiceProviderSelector: React.FC<{
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
border: '1px solid rgba(96, 214, 239, 0.4)',
|
||||
},
|
||||
}}
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'service-provider-button',
|
||||
sx: {
|
||||
@@ -120,7 +130,18 @@ export const ServiceProviderSelector: React.FC<{
|
||||
}}
|
||||
>
|
||||
{servicesWithRandomSp.map(({ id, description, sp }) => (
|
||||
<MenuItem dense key={id} sx={{ fontSize: 'small', fontWeight: 'bold' }} onClick={() => handleClose(sp)}>
|
||||
<MenuItem
|
||||
dense
|
||||
autoFocus={id === service?.id}
|
||||
key={id}
|
||||
sx={{
|
||||
fontSize: 'small',
|
||||
fontWeight: 'bold',
|
||||
minWidth: '208px',
|
||||
'&.Mui-focusVisible': { bgcolor: 'transparent' },
|
||||
}}
|
||||
onClick={() => handleClose(sp)}
|
||||
>
|
||||
<Tooltip
|
||||
title={
|
||||
<Stack direction="column">
|
||||
@@ -143,6 +164,16 @@ export const ServiceProviderSelector: React.FC<{
|
||||
>
|
||||
<Typography>{description}</Typography>
|
||||
</Tooltip>
|
||||
{id === service?.id && (
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: '0',
|
||||
}}
|
||||
>
|
||||
<Check sx={{ padding: 0 }} />
|
||||
</ListItemIcon>
|
||||
)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
|
||||
@@ -24,17 +24,17 @@ const nymPalette: NymPalette = {
|
||||
success: '#21D073',
|
||||
info: '#60D7EF',
|
||||
fee: '#967FF0',
|
||||
background: { light: '#F4F6F8', dark: '#121726' },
|
||||
background: { light: '#F4F6F8', dark: '#1D2125' },
|
||||
text: {
|
||||
light: '#F2F2F2',
|
||||
dark: '#121726',
|
||||
dark: '#1D2125',
|
||||
},
|
||||
};
|
||||
|
||||
const darkMode: NymPaletteVariant = {
|
||||
mode: 'dark',
|
||||
background: {
|
||||
main: '#121726',
|
||||
main: '#1D2125',
|
||||
paper: '#242C3D',
|
||||
},
|
||||
text: {
|
||||
@@ -52,7 +52,7 @@ const lightMode: NymPaletteVariant = {
|
||||
paper: '#FFFFFF',
|
||||
},
|
||||
text: {
|
||||
main: '#121726',
|
||||
main: '#1D2125',
|
||||
},
|
||||
topNav: {
|
||||
background: '#111826',
|
||||
|
||||
+15
-3
@@ -1,8 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
## [nym-wallet-v1.1.0](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.0) (2022-11-09)
|
||||
|
||||
- Add window for showing logs for when the terminal is not available.
|
||||
- wallet: Add window for showing logs for when the terminal is not available.
|
||||
- wallet: Rework of rewarding ([#1472])
|
||||
- wallet: Highlight delegations on an unbonded node to notify the delegator to undelegate stake
|
||||
- wallet: Group delegations on unbonded nodes at the top of the list
|
||||
- wallet: Simplified delegation screen when user has no delegations
|
||||
- wallet: Update "create password" howto guide
|
||||
- wallet: Allow setting of operating cost when node is bonded
|
||||
- wallet: Allow update of operating cost on bonded node
|
||||
- wallet: New bond settings area
|
||||
- wallet: Display pending unbonds
|
||||
- wallet: Display routing score and average score for gateways
|
||||
- wallet: Improve dialogs for account creation
|
||||
|
||||
[#1472]: https://github.com/nymtech/nym/pull/1472
|
||||
|
||||
## [nym-wallet-v1.0.9](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.0.8) (2022-09-08)
|
||||
|
||||
@@ -14,7 +27,6 @@
|
||||
- wallet: compound and redeem functionalities for operator rewards
|
||||
- wallet: a few minor touch ups and bug fixes
|
||||
|
||||
|
||||
## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11)
|
||||
|
||||
- wallet: dark mode
|
||||
|
||||
Generated
+5
-6
@@ -793,9 +793,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -811,9 +811,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.2"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
|
||||
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
@@ -2877,7 +2877,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym_wallet"
|
||||
version = "1.0.9"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2 0.3.4",
|
||||
@@ -5423,7 +5423,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
||||
@@ -160,9 +160,9 @@ mod qa {
|
||||
pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
|
||||
|
||||
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
|
||||
"n1frq2hzkjtatsupc6jtyaz67ytydk9nya437q92qg76ny3y8fcnjsw806vg";
|
||||
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
|
||||
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
|
||||
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
|
||||
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
|
||||
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
|
||||
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
|
||||
@@ -177,8 +177,8 @@ mod qa {
|
||||
//pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
|
||||
pub(crate) fn validators() -> Vec<ValidatorDetails> {
|
||||
vec![ValidatorDetails::new(
|
||||
"https://adv-epoch-qa-validator.qa.nymte.ch/",
|
||||
Some("https://adv-epoch-qa-val-api.qa.nymte.ch/api"),
|
||||
"https://qwerty-validator.qa.nymte.ch/",
|
||||
Some("https://qwerty-validator-api.qa.nymte.ch/api"),
|
||||
)]
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
"eslint-plugin-react": "^7.29.2",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-storybook": "^0.5.12",
|
||||
"favicons": "^6.2.2",
|
||||
"favicons": "^7.0.2",
|
||||
"favicons-webpack-plugin": "^5.0.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^7.2.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym_wallet"
|
||||
version = "1.0.9"
|
||||
version = "1.1.0"
|
||||
description = "Nym Native Wallet"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -72,7 +72,7 @@ mod base64 {
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
|
||||
let s = <String>::deserialize(deserializer)?;
|
||||
base64::decode(&s).map_err(serde::de::Error::custom)
|
||||
base64::decode(s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-wallet",
|
||||
"version": "1.0.9"
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -42,7 +42,7 @@ const headers: Header[] = [
|
||||
header: 'Operator rewards',
|
||||
id: 'operator-rewards',
|
||||
tooltipText:
|
||||
'This is your (operator) new rewards including the PM and cost. You can compound your rewards manually every epoch or unbond your node to redeem them.',
|
||||
'This is your (operator) rewards including the PM and cost. Rewards are automatically compounded every epoch. You can redeem your rewards at any time.',
|
||||
},
|
||||
{
|
||||
header: 'No. delegators',
|
||||
|
||||
@@ -179,7 +179,7 @@ const AmountFormData = ({
|
||||
<CurrencyFormField
|
||||
required
|
||||
fullWidth
|
||||
label="Operator cost"
|
||||
label="Operator Cost"
|
||||
autoFocus
|
||||
onChanged={(newValue) => setValue('operatorCost', newValue, { shouldValidate: true })}
|
||||
validationError={errors.operatorCost?.amount?.message}
|
||||
|
||||
@@ -130,16 +130,10 @@ const TokenTransfer = () => {
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="subtitle2" sx={{ color: (t) => t.palette.nym.text.muted, mt: 2 }}>
|
||||
Transferable tokens
|
||||
Unlocked transferable tokens
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
data-testid="refresh-success"
|
||||
sx={{ color: 'text.primary' }}
|
||||
variant="h5"
|
||||
fontWeight="700"
|
||||
textTransform="uppercase"
|
||||
>
|
||||
<Typography data-testid="refresh-success" sx={{ color: 'text.primary' }} variant="h5" textTransform="uppercase">
|
||||
{userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.display_mix_denom.toUpperCase()}
|
||||
</Typography>
|
||||
</Grid>
|
||||
@@ -167,7 +161,6 @@ export const VestingCard = ({ onTransfer }: { onTransfer: () => Promise<void> })
|
||||
<NymCard
|
||||
title="Vesting Schedule"
|
||||
data-testid="check-unvested-tokens"
|
||||
Icon={<InfoOutlined />}
|
||||
Action={
|
||||
<IconButton
|
||||
onClick={async () => {
|
||||
|
||||
@@ -33,10 +33,17 @@ export const NodeUnbondPage = ({ bondedNode, onConfirm, onError }: Props) => {
|
||||
</Grid>
|
||||
<Grid item container direction={'column'} spacing={2} width={0.5} padding={3}>
|
||||
<Grid item>
|
||||
<Box sx={{ mb: 1 }}>
|
||||
<Error
|
||||
message={`Remember you should only unbond if you want to remove your ${
|
||||
isMixnode(bondedNode) ? 'node' : 'gateway'
|
||||
} from the network for good.`}
|
||||
/>
|
||||
</Box>
|
||||
<Error
|
||||
message={`Unbonding is irreversible and it won’t be possible to restore the current state of your ${
|
||||
isMixnode(bondedNode) ? 'node' : 'gateway'
|
||||
} again`}
|
||||
} again.`}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
|
||||
+37
-41
@@ -4,15 +4,20 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Button, Divider, Typography, TextField, Grid, CircularProgress, Box } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { isMixnode } from 'src/types';
|
||||
import { updateMixnodeConfig } from 'src/requests';
|
||||
import { simulateUpdateMixnodeConfig, simulateVestingUpdateMixnodeConfig, updateMixnodeConfig } from 'src/requests';
|
||||
import { TBondedMixnode, TBondedGateway } from 'src/context/bonding';
|
||||
import { SimpleModal } from 'src/components/Modals/SimpleModal';
|
||||
import { bondedInfoParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { Alert } from 'src/components/Alert';
|
||||
import { vestingUpdateMixnodeConfig } from 'src/requests/vesting';
|
||||
import { ConfirmTx } from 'src/components/ConfirmTX';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
|
||||
export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => {
|
||||
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
|
||||
const { getFee, fee, resetFeeState } = useGetFee();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -33,6 +38,7 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
|
||||
verlocPort?: number;
|
||||
httpApiPort?: number;
|
||||
}) => {
|
||||
resetFeeState();
|
||||
const { host, version, mixPort, verlocPort, httpApiPort } = data;
|
||||
if (host && version && mixPort && verlocPort && httpApiPort) {
|
||||
const MixNodeConfigParams = {
|
||||
@@ -43,7 +49,11 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
|
||||
version,
|
||||
};
|
||||
try {
|
||||
await updateMixnodeConfig(MixNodeConfigParams);
|
||||
if (bondedNode.proxy) {
|
||||
await vestingUpdateMixnodeConfig(MixNodeConfigParams);
|
||||
} else {
|
||||
await updateMixnodeConfig(MixNodeConfigParams);
|
||||
}
|
||||
setOpenConfirmationModal(true);
|
||||
} catch (error) {
|
||||
Console.error(error);
|
||||
@@ -53,10 +63,22 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
|
||||
|
||||
return (
|
||||
<Grid container xs item>
|
||||
{fee && (
|
||||
<ConfirmTx
|
||||
open
|
||||
header="Update node settings"
|
||||
fee={fee}
|
||||
onConfirm={handleSubmit((d) => onSubmit(d))}
|
||||
onPrev={resetFeeState}
|
||||
onClose={resetFeeState}
|
||||
/>
|
||||
)}
|
||||
{isSubmitting && <LoadingModal />}
|
||||
<Alert
|
||||
title={
|
||||
<Box sx={{ fontWeight: 600 }}>
|
||||
Your changes will be ONLY saved on the display. Remember to change the values on your node’s config file too
|
||||
Changing these values will ONLY change the data about your node on the blockchain. Remember to change your
|
||||
node’s config file with the same values too
|
||||
</Box>
|
||||
}
|
||||
dismissable
|
||||
@@ -67,16 +89,6 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
|
||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
Port
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
mb: 2,
|
||||
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
|
||||
}}
|
||||
>
|
||||
Change profit margin of your node
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
|
||||
<Grid item width={1}>
|
||||
@@ -120,16 +132,6 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
|
||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
Host
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
mb: 2,
|
||||
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
|
||||
}}
|
||||
>
|
||||
Lock wallet after certain time
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
|
||||
<Grid item width={1}>
|
||||
@@ -151,16 +153,6 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
|
||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
Version
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
mb: 2,
|
||||
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
|
||||
}}
|
||||
>
|
||||
Lock wallet after certain time
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
|
||||
<Grid item width={1}>
|
||||
@@ -182,18 +174,24 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
|
||||
size="large"
|
||||
variant="contained"
|
||||
disabled={isSubmitting || !isDirty || !isValid}
|
||||
onClick={handleSubmit((d) => onSubmit(d))}
|
||||
type="submit"
|
||||
sx={{ m: 3, width: '320px' }}
|
||||
endIcon={isSubmitting && <CircularProgress size={20} />}
|
||||
onClick={handleSubmit((data) =>
|
||||
getFee(bondedNode.proxy ? simulateVestingUpdateMixnodeConfig : simulateUpdateMixnodeConfig, {
|
||||
host: data.host,
|
||||
mix_port: data.mixPort,
|
||||
verloc_port: data.verlocPort,
|
||||
http_api_port: data.httpApiPort,
|
||||
version: data.version,
|
||||
}),
|
||||
)}
|
||||
sx={{ m: 3 }}
|
||||
>
|
||||
Save all display changes
|
||||
Submit changes to the blockchain
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<SimpleModal
|
||||
open={openConfirmationModal}
|
||||
header="Your changes were ONLY saved on the display"
|
||||
header="Your changes are submitted to the blockchain"
|
||||
subHeader="Remember to change the values
|
||||
on your node’s config file too."
|
||||
okLabel="close"
|
||||
@@ -216,7 +214,6 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
|
||||
textAlign: 'center',
|
||||
color: theme.palette.nym.nymWallet.text.blue,
|
||||
fontSize: 16,
|
||||
textTransform: 'capitalize',
|
||||
}}
|
||||
subHeaderStyles={{
|
||||
width: '100%',
|
||||
@@ -224,7 +221,6 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
|
||||
textAlign: 'center',
|
||||
color: 'main',
|
||||
fontSize: 14,
|
||||
textTransform: 'capitalize',
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
+42
-11
@@ -14,17 +14,27 @@ import {
|
||||
} from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { add, format, fromUnixTime } from 'date-fns';
|
||||
import { isMixnode } from 'src/types';
|
||||
import { getCurrentInterval, getPendingIntervalEvents, updateMixnodeCostParams } from 'src/requests';
|
||||
import { TBondedMixnode, TBondedGateway } from 'src/context/bonding';
|
||||
import {
|
||||
getCurrentInterval,
|
||||
getPendingIntervalEvents,
|
||||
simulateUpdateMixnodeCostParams,
|
||||
simulateVestingUpdateMixnodeCostParams,
|
||||
updateMixnodeCostParams,
|
||||
vestingUpdateMixnodeCostParams,
|
||||
} from 'src/requests';
|
||||
import { TBondedMixnode } from 'src/context/bonding';
|
||||
import { SimpleModal } from 'src/components/Modals/SimpleModal';
|
||||
import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { Alert } from 'src/components/Alert';
|
||||
import { ChangeMixCostParams } from 'src/pages/bonding/types';
|
||||
import { AppContext } from 'src/context';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { ConfirmTx } from 'src/components/ConfirmTX';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
|
||||
export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => {
|
||||
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
|
||||
@@ -34,6 +44,8 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
const theme = useTheme();
|
||||
|
||||
const { fee, getFee, resetFeeState } = useGetFee();
|
||||
|
||||
const defaultValues = {
|
||||
operatorCost: bondedNode.operatorCost,
|
||||
profitMargin: bondedNode.profitMargin,
|
||||
@@ -96,6 +108,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
}, []);
|
||||
|
||||
const onSubmit = async (data: { operatorCost: { amount: string; denom: CurrencyDenom }; profitMargin: string }) => {
|
||||
resetFeeState();
|
||||
if (data.operatorCost && data.profitMargin) {
|
||||
const MixNodeCostParams = {
|
||||
profit_margin_percent: (+data.profitMargin / 100).toString(),
|
||||
@@ -105,7 +118,11 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
},
|
||||
};
|
||||
try {
|
||||
await updateMixnodeCostParams(MixNodeCostParams);
|
||||
if (bondedNode.proxy) {
|
||||
await vestingUpdateMixnodeCostParams(MixNodeCostParams);
|
||||
} else {
|
||||
await updateMixnodeCostParams(MixNodeCostParams);
|
||||
}
|
||||
await getPendingEvents();
|
||||
reset();
|
||||
setOpenConfirmationModal(true);
|
||||
@@ -117,6 +134,17 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
|
||||
return (
|
||||
<Grid container xs item>
|
||||
{fee && (
|
||||
<ConfirmTx
|
||||
open
|
||||
header="Update cost parameters"
|
||||
fee={fee}
|
||||
onConfirm={handleSubmit((d) => onSubmit(d))}
|
||||
onPrev={resetFeeState}
|
||||
onClose={resetFeeState}
|
||||
/>
|
||||
)}
|
||||
{isSubmitting && <LoadingModal />}
|
||||
<Alert
|
||||
title={
|
||||
<>
|
||||
@@ -129,7 +157,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
/>
|
||||
<Grid container direction="column">
|
||||
<Grid item container alignItems="left" justifyContent="space-between" padding={3} spacing={1}>
|
||||
<Grid item>
|
||||
<Grid item xl={6}>
|
||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
Profit Margin
|
||||
</Typography>
|
||||
@@ -223,12 +251,16 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
size="large"
|
||||
variant="contained"
|
||||
disabled={isSubmitting || !isDirty || !isValid}
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
onClick={handleSubmit((data) => {
|
||||
getFee(bondedNode.proxy ? simulateVestingUpdateMixnodeCostParams : simulateUpdateMixnodeCostParams, {
|
||||
profit_margin_percent: (+data.profitMargin / 100).toString(),
|
||||
interval_operating_cost: data.operatorCost,
|
||||
});
|
||||
})}
|
||||
type="submit"
|
||||
sx={{ m: 3, width: '320px' }}
|
||||
endIcon={isSubmitting && <CircularProgress size={20} />}
|
||||
sx={{ m: 3 }}
|
||||
>
|
||||
Save all display changes
|
||||
Submit changes to the blockchain
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -236,7 +268,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
open={openConfirmationModal}
|
||||
header="Your changes will take place
|
||||
in the next interval"
|
||||
okLabel="close"
|
||||
okLabel="Close"
|
||||
hideCloseIcon
|
||||
displayInfoIcon
|
||||
onOk={async () => {
|
||||
@@ -256,7 +288,6 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
textAlign: 'center',
|
||||
color: theme.palette.nym.nymWallet.text.blue,
|
||||
fontSize: 16,
|
||||
textTransform: 'capitalize',
|
||||
}}
|
||||
subHeaderStyles={{
|
||||
m: 0,
|
||||
|
||||
@@ -152,7 +152,7 @@ export const SystemVariables = ({
|
||||
|
||||
<DataField
|
||||
title="Node stake saturation"
|
||||
info="Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 1 million NYM, computed as S/K where S is the total amount of tokens available to stakeholders and K is the number of nodes in the reward set."
|
||||
info="Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 750k NYM, computed as S/K where S is the total amount of tokens available to stakeholders and K is the number of nodes in the reward set."
|
||||
Indicator={<PercentIndicator value={saturation} warning={saturation >= 100} />}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"private": true,
|
||||
"license": "Apache 2.0",
|
||||
"workspaces": [
|
||||
"sdk/typescript/**",
|
||||
"ts-packages/*",
|
||||
"nym-wallet",
|
||||
"nym-connect",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Nym SDK
|
||||
|
||||
Welcome to the Nym SDK. This is a good starting point for finding resources to build software using Nym components.
|
||||
|
||||
The SDK is split into technologies and platforms:
|
||||
|
||||
- [Typescript](typescript) - packages for the Javascript ecosystem leveraging Nym's Typescript and WASM clients. Use these to build browser apps, web apps and mobile apps that can make use of the Nym mixnet and Coconut credentials
|
||||
|
||||
Coming soon:
|
||||
|
||||
- [iOS](ios) - native libraries for iOS, that can be used directly or from React Native using wrappers
|
||||
- [Android](android) - native libraries for iOS, that can be used via JNI or from React Native using wrappers
|
||||
@@ -0,0 +1,18 @@
|
||||
# See http://editorconfig.org/
|
||||
# EditorConfig helps developers define and maintain consistent coding styles
|
||||
# between different editors and IDEs. The EditorConfig project consists of a
|
||||
# file format for defining coding styles and a collection of text editor plugins
|
||||
# that enable editors to read the file format and adhere to defined styles.
|
||||
# EditorConfig files are easily readable and they work nicely with version
|
||||
# control systems.
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user