Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 84e7bbd8ea | |||
| e30fd270a1 | |||
| e3cda93919 | |||
| 87fb4daeda | |||
| 9f56796bf6 | |||
| 09b51226c2 | |||
| 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 | |||
| b8cb683da0 | |||
| 62e9c8236a | |||
| d79c25861b | |||
| b89ec2e0be | |||
| 269f50bdd4 | |||
| e3c02dc80a | |||
| 65a9320d35 | |||
| cf268ffcd5 | |||
| bdcfe42a1e | |||
| 51e30b2a89 | |||
| 76d4d0e7cb | |||
| 9df432b8a2 | |||
| 4021059e76 | |||
| 43ef098aad | |||
| bf1d2a12bc | |||
| c3f214ffad | |||
| 324fb6afe7 | |||
| ace020b5cf | |||
| 4b8fa4805e | |||
| eb18b49f3e | |||
| 2dc45fda1e | |||
| c2a113f1b3 | |||
| f805eebce7 | |||
| ad81160760 | |||
| 0931236a98 | |||
| 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
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
name: Tests for validator API
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "validator-api/tests/**"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: validator-api/tests
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: validator api tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Node v18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.1.0
|
||||
|
||||
- name: Install yarn
|
||||
run: yarn install
|
||||
|
||||
- name: Run yarn
|
||||
run: yarn
|
||||
|
||||
- name: Launch tests
|
||||
run: yarn test
|
||||
working-directory: validator-api/tests
|
||||
+41
-27
@@ -6,35 +6,50 @@ 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`
|
||||
- websocket-requests: add server response signalling current packet queue length in the client
|
||||
|
||||
### 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 +57,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
|
||||
|
||||
@@ -122,6 +125,3 @@ mixnet-opt: wasm
|
||||
generate-typescript:
|
||||
cd tools/ts-rs-cli && cargo run && cd ../..
|
||||
yarn types:lint:fix
|
||||
|
||||
run-validator-tests:
|
||||
cd validator-api/tests/functional_test && yarn test
|
||||
@@ -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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,14 @@ pub const RECEIVED_RESPONSE_TAG: u8 = 0x01;
|
||||
/// Value tag representing [`SelfAddress`] variant of the [`ServerResponse`]
|
||||
pub const SELF_ADDRESS_RESPONSE_TAG: u8 = 0x02;
|
||||
|
||||
/// Value tag representing [`LaneQueueLength`] variant of the [`ServerResponse`]
|
||||
pub const LANE_QUEUE_LENGTH_RESPONSE_TAG: u8 = 0x03;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServerResponse {
|
||||
Received(ReconstructedMessage),
|
||||
SelfAddress(Recipient),
|
||||
LaneQueueLength(u64, usize),
|
||||
Error(error::Error),
|
||||
}
|
||||
|
||||
@@ -193,6 +197,31 @@ impl ServerResponse {
|
||||
Ok(ServerResponse::SelfAddress(recipient))
|
||||
}
|
||||
|
||||
// LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length
|
||||
fn serialize_lane_queue_length(lane: u64, queue_length: usize) -> Vec<u8> {
|
||||
std::iter::once(LANE_QUEUE_LENGTH_RESPONSE_TAG)
|
||||
.chain(lane.to_be_bytes().iter().cloned())
|
||||
.chain(queue_length.to_be_bytes().iter().cloned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length
|
||||
fn deserialize_lane_queue_length(b: &[u8]) -> Result<Self, error::Error> {
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], LANE_QUEUE_LENGTH_RESPONSE_TAG);
|
||||
|
||||
let mut lane_bytes = [0u8; size_of::<u64>()];
|
||||
lane_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
|
||||
let lane = u64::from_be_bytes(lane_bytes);
|
||||
|
||||
let mut queue_length_bytes = [0u8; size_of::<usize>()];
|
||||
queue_length_bytes
|
||||
.copy_from_slice(&b[1 + size_of::<u64>()..1 + size_of::<u64>() + size_of::<usize>()]);
|
||||
let queue_length = usize::from_be_bytes(queue_length_bytes);
|
||||
|
||||
Ok(ServerResponse::LaneQueueLength(lane, queue_length))
|
||||
}
|
||||
|
||||
// ERROR_RESPONSE_TAG || err_code || msg_len || msg
|
||||
fn serialize_error(error: error::Error) -> Vec<u8> {
|
||||
let message_len_bytes = (error.message.len() as u64).to_be_bytes();
|
||||
@@ -272,6 +301,9 @@ impl ServerResponse {
|
||||
Self::serialize_received(reconstructed_message)
|
||||
}
|
||||
ServerResponse::SelfAddress(address) => Self::serialize_self_address(address),
|
||||
ServerResponse::LaneQueueLength(lane, queue_length) => {
|
||||
Self::serialize_lane_queue_length(lane, queue_length)
|
||||
}
|
||||
ServerResponse::Error(err) => Self::serialize_error(err),
|
||||
}
|
||||
}
|
||||
@@ -302,6 +334,7 @@ impl ServerResponse {
|
||||
match response_tag {
|
||||
RECEIVED_RESPONSE_TAG => Self::deserialize_received(b),
|
||||
SELF_ADDRESS_RESPONSE_TAG => Self::deserialize_self_address(b),
|
||||
LANE_QUEUE_LENGTH_RESPONSE_TAG => Self::deserialize_lane_queue_length(b),
|
||||
ERROR_RESPONSE_TAG => Self::deserialize_error(b),
|
||||
n => Err(error::Error::new(
|
||||
ErrorKind::UnknownResponse,
|
||||
@@ -378,6 +411,20 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lane_queue_length_response_serialization_works() {
|
||||
let lane_queue_length_response = ServerResponse::LaneQueueLength(13, 42);
|
||||
let bytes = lane_queue_length_response.serialize();
|
||||
let recovered = ServerResponse::deserialize(&bytes).unwrap();
|
||||
match recovered {
|
||||
ServerResponse::LaneQueueLength(lane, queue_length) => {
|
||||
assert_eq!(lane, 13);
|
||||
assert_eq!(queue_length, 42)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_response_serialization_works() {
|
||||
let dummy_error = error::Error::new(ErrorKind::UnknownRequest, "foomp message".to_string());
|
||||
|
||||
@@ -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),
|
||||
@@ -91,6 +94,10 @@ pub(super) enum ServerResponseText {
|
||||
SelfAddress {
|
||||
address: String,
|
||||
},
|
||||
LaneQueueLength {
|
||||
lane: u64,
|
||||
queue_length: usize,
|
||||
},
|
||||
Error {
|
||||
message: String,
|
||||
},
|
||||
@@ -132,6 +139,9 @@ impl From<ServerResponse> for ServerResponseText {
|
||||
ServerResponse::SelfAddress(recipient) => ServerResponseText::SelfAddress {
|
||||
address: recipient.to_string(),
|
||||
},
|
||||
ServerResponse::LaneQueueLength(lane, queue_length) => {
|
||||
ServerResponseText::LaneQueueLength { lane, queue_length }
|
||||
}
|
||||
ServerResponse::Error(err) => ServerResponseText::Error {
|
||||
message: err.to_string(),
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "nym-client-wasm"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
edition = "2021"
|
||||
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
|
||||
license = "Apache-2.0"
|
||||
@@ -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>;
|
||||
@@ -109,6 +109,12 @@ pub trait VestingSigningClient {
|
||||
cap: Option<PledgeCap>,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
|
||||
async fn transfer_ownership(
|
||||
&self,
|
||||
to_address: &str,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -405,4 +411,25 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn transfer_ownership(
|
||||
&self,
|
||||
to_address: &str,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
let req = VestingExecuteMsg::TransferOwnership {
|
||||
to_address: to_address.to_string(),
|
||||
};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.vesting_contract_address(),
|
||||
&req,
|
||||
fee,
|
||||
"VestingContract::TransferOwnership",
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,29 +61,18 @@ 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)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PledgeCap {
|
||||
fn default() -> Self {
|
||||
#[allow(clippy::expect_used)]
|
||||
PledgeCap::Percent(Percent::from_percentage_value(10).expect("This can never fail!"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::queued_migrations::migrate_to_v2_mixnet_contract;
|
||||
use crate::storage::{
|
||||
account_from_address, BlockTimestampSecs, ADMIN, DELEGATIONS, MIXNET_CONTRACT_ADDRESS,
|
||||
MIX_DENOM,
|
||||
account_from_address, save_account, BlockTimestampSecs, ADMIN, DELEGATIONS,
|
||||
MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
|
||||
};
|
||||
use crate::traits::{
|
||||
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount,
|
||||
@@ -158,7 +158,7 @@ pub fn try_update_locked_pledge_cap(
|
||||
let mut account = account_from_address(&address, deps.storage, deps.api)?;
|
||||
|
||||
account.pledge_cap = Some(cap);
|
||||
// update_locked_pledge_cap(amount, deps.storage)?;
|
||||
save_account(&account, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
CONFIGURED=true
|
||||
|
||||
RUST_LOG=info
|
||||
RUST_BACKTRACE=1
|
||||
|
||||
BECH32_PREFIX=n
|
||||
MIX_DENOM=unym
|
||||
MIX_DENOM_DISPLAY=nym
|
||||
STAKE_DENOM=unyx
|
||||
STAKE_DENOM_DISPLAY=nyx
|
||||
DENOMS_EXPONENT=6
|
||||
MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g
|
||||
VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw
|
||||
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
|
||||
NYMD_VALIDATOR="https://qwerty-validator.qa.nymte.ch/"
|
||||
API_VALIDATOR="https://qwerty-validator-api.qa.nymte.ch/api"
|
||||
+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,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -36,16 +36,16 @@ export const useMixnodeContext = (): React.ContextType<typeof MixnodeContext> =>
|
||||
React.useContext<MixnodeState>(MixnodeContext);
|
||||
|
||||
interface MixnodeContextProviderProps {
|
||||
mixNodeIdentityKey: string;
|
||||
mixId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a state context for a mixnode by identity
|
||||
* @param mixNodeIdentityKey The identity key of the mixnode
|
||||
* @param mixId The mixID of the mixnode
|
||||
*/
|
||||
export const MixnodeContextProvider: React.FC<MixnodeContextProviderProps> = ({ mixNodeIdentityKey, children }) => {
|
||||
export const MixnodeContextProvider: React.FC<MixnodeContextProviderProps> = ({ mixId, children }) => {
|
||||
const [mixNode, fetchMixnodeById, clearMixnodeById] = useApiState<MixNodeResponseItem | undefined>(
|
||||
mixNodeIdentityKey,
|
||||
mixId,
|
||||
Api.fetchMixnodeByID,
|
||||
'Failed to fetch mixnode by id',
|
||||
);
|
||||
@@ -53,44 +53,44 @@ export const MixnodeContextProvider: React.FC<MixnodeContextProviderProps> = ({
|
||||
const [mixNodeRow, setMixnodeRow] = React.useState<MixnodeRowType | undefined>();
|
||||
|
||||
const [delegations, fetchDelegations, clearDelegations] = useApiState<DelegationsResponse>(
|
||||
mixNodeIdentityKey,
|
||||
mixId,
|
||||
Api.fetchDelegationsById,
|
||||
'Failed to fetch delegations for mixnode',
|
||||
);
|
||||
|
||||
const [uniqDelegations, fetchUniqDelegations, clearUniqDelegations] = useApiState<UniqDelegationsResponse>(
|
||||
mixNodeIdentityKey,
|
||||
mixId,
|
||||
Api.fetchUniqDelegationsById,
|
||||
'Failed to fetch delegations for mixnode',
|
||||
);
|
||||
|
||||
const [status, fetchStatus, clearStatus] = useApiState<StatusResponse>(
|
||||
mixNodeIdentityKey,
|
||||
mixId,
|
||||
Api.fetchStatusById,
|
||||
'Failed to fetch mixnode status',
|
||||
);
|
||||
|
||||
const [stats, fetchStats, clearStats] = useApiState<StatsResponse>(
|
||||
mixNodeIdentityKey,
|
||||
mixId,
|
||||
Api.fetchStatsById,
|
||||
'Failed to fetch mixnode stats',
|
||||
);
|
||||
|
||||
const [description, fetchDescription, clearDescription] = useApiState<MixNodeDescriptionResponse>(
|
||||
mixNodeIdentityKey,
|
||||
mixId,
|
||||
Api.fetchMixnodeDescriptionById,
|
||||
'Failed to fetch mixnode description',
|
||||
);
|
||||
|
||||
const [economicDynamicsStats, fetchEconomicDynamicsStats, clearEconomicDynamicsStats] =
|
||||
useApiState<MixNodeEconomicDynamicsStatsResponse>(
|
||||
mixNodeIdentityKey,
|
||||
mixId,
|
||||
Api.fetchMixnodeEconomicDynamicsStatsById,
|
||||
'Failed to fetch mixnode dynamics stats by id',
|
||||
);
|
||||
|
||||
const [uptimeStory, fetchUptimeHistory, clearUptimeHistory] = useApiState<UptimeStoryResponse>(
|
||||
mixNodeIdentityKey,
|
||||
mixId,
|
||||
Api.fetchUptimeStoryById,
|
||||
'Failed to fetch mixnode uptime history',
|
||||
);
|
||||
@@ -123,7 +123,7 @@ export const MixnodeContextProvider: React.FC<MixnodeContextProviderProps> = ({
|
||||
fetchUptimeHistory(),
|
||||
]);
|
||||
});
|
||||
}, [mixNodeIdentityKey]);
|
||||
}, [mixId]);
|
||||
|
||||
const state = React.useMemo<MixnodeState>(
|
||||
() => ({
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
@@ -223,7 +223,7 @@ export const PageMixnodeDetail: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<MixnodeContextProvider mixNodeIdentityKey={id}>
|
||||
<MixnodeContextProvider mixId={id}>
|
||||
<PageMixnodeDetailGuard />
|
||||
</MixnodeContextProvider>
|
||||
);
|
||||
|
||||
@@ -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
-1983
File diff suppressed because it is too large
Load Diff
@@ -1,28 +0,0 @@
|
||||
[package]
|
||||
name = "chitchat-test"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
chitchat = "0.4"
|
||||
poem = "1"
|
||||
poem-openapi = {version="2", features = ["swagger-ui"] }
|
||||
structopt = "0.3"
|
||||
tokio = { version = "1.14.0", features = ["net", "sync", "rt-multi-thread", "macros", "time"] }
|
||||
serde = { version="1", features=["derive"] }
|
||||
serde_json = "1"
|
||||
anyhow = "1"
|
||||
once_cell = "1"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
cool-id-generator = "1"
|
||||
env_logger = "0.9"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2"
|
||||
predicates = "2"
|
||||
reqwest = { version = "0.11", default-features=false, features = ["blocking", "json"] }
|
||||
|
||||
[workspace]
|
||||
@@ -1,15 +0,0 @@
|
||||
# Chitchat test
|
||||
|
||||
Runs simple chitchat servers, mostly copied over from https://github.com/quickwit-oss/chitchat
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
# Starts 5 servers and joins them into a cluster on localhost ports 10000-10004
|
||||
# All servers print cluster state on `/` ie 127.0.0.1:10000
|
||||
# `/docs` endpoint has an open api with a key value setter, set it on one node and observe how the state propagates to the other nodes
|
||||
# NodeState is a regular BTreeMap
|
||||
./run-servers.sh
|
||||
|
||||
# run killall chitchat-test after you're done, as the servers will continue to run forever in the background
|
||||
```
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
killall chitchat-test
|
||||
|
||||
cargo build --release
|
||||
|
||||
for i in $(seq 10000 10004)
|
||||
do
|
||||
listen_addr="127.0.0.1:$i";
|
||||
echo ${listen_addr};
|
||||
cargo run --release -- --listen_addr ${listen_addr} --seed 127.0.0.1:10000 --node_id node_$i &
|
||||
done;
|
||||
|
||||
read
|
||||
kill 0
|
||||
@@ -1,123 +0,0 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use chitchat::transport::UdpTransport;
|
||||
use chitchat::{spawn_chitchat, Chitchat, ChitchatConfig, FailureDetectorConfig, NodeId};
|
||||
use cool_id_generator::Size;
|
||||
use poem::listener::TcpListener;
|
||||
use poem::{Route, Server};
|
||||
use poem_openapi::param::Query;
|
||||
use poem_openapi::payload::Json;
|
||||
use poem_openapi::{OpenApi, OpenApiService};
|
||||
use structopt::StructOpt;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use chitchat::ClusterStateSnapshot;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ApiResponse {
|
||||
pub cluster_id: String,
|
||||
pub cluster_state: ClusterStateSnapshot,
|
||||
pub live_nodes: Vec<NodeId>,
|
||||
pub dead_nodes: Vec<NodeId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SetKeyValueResponse {
|
||||
pub status: bool,
|
||||
}
|
||||
|
||||
struct Api {
|
||||
chitchat: Arc<Mutex<Chitchat>>,
|
||||
}
|
||||
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
/// Chitchat state
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn index(&self) -> Json<serde_json::Value> {
|
||||
let chitchat_guard = self.chitchat.lock().await;
|
||||
let response = ApiResponse {
|
||||
cluster_id: chitchat_guard.cluster_id().to_string(),
|
||||
cluster_state: chitchat_guard.state_snapshot(),
|
||||
live_nodes: chitchat_guard.live_nodes().cloned().collect::<Vec<_>>(),
|
||||
dead_nodes: chitchat_guard.dead_nodes().cloned().collect::<Vec<_>>(),
|
||||
};
|
||||
Json(serde_json::to_value(&response).unwrap())
|
||||
}
|
||||
|
||||
/// Set a key & value on this node (with no validation).
|
||||
#[oai(path = "/set_kv/", method = "get")]
|
||||
async fn set_kv(&self, key: Query<String>, value: Query<String>) -> Json<serde_json::Value> {
|
||||
let mut chitchat_guard = self.chitchat.lock().await;
|
||||
|
||||
let cc_state = chitchat_guard.self_node_state();
|
||||
cc_state.set(key.as_str(), value.as_str());
|
||||
|
||||
Json(serde_json::to_value(&SetKeyValueResponse { status: true }).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "chitchat", about = "Chitchat test server.")]
|
||||
struct Opt {
|
||||
/// Defines the socket addr on which we should listen to.
|
||||
#[structopt(long = "listen_addr", default_value = "127.0.0.1:10000")]
|
||||
listen_addr: SocketAddr,
|
||||
/// Defines the socket_address (host:port) other servers should use to
|
||||
/// reach this server.
|
||||
///
|
||||
/// It defaults to the listen address, but this is only valid
|
||||
/// when all server are running on the same server.
|
||||
#[structopt(long = "public_addr")]
|
||||
public_addr: Option<SocketAddr>,
|
||||
|
||||
/// Node id. Has to be unique. If None, the node_id will be generated from
|
||||
/// the public_addr and a random suffix.
|
||||
#[structopt(long = "node_id")]
|
||||
node_id: Option<String>,
|
||||
|
||||
#[structopt(long = "seed")]
|
||||
seeds: Vec<String>,
|
||||
|
||||
#[structopt(long = "interval_ms", default_value = "500")]
|
||||
interval: u64,
|
||||
}
|
||||
|
||||
fn generate_server_id(public_addr: SocketAddr) -> String {
|
||||
let cool_id = cool_id_generator::get_id(Size::Medium);
|
||||
format!("server:{}-{}", public_addr, cool_id)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
let opt = Opt::from_args();
|
||||
println!("{:?}", opt);
|
||||
let public_addr = opt.public_addr.unwrap_or(opt.listen_addr);
|
||||
let node_id_str = opt
|
||||
.node_id
|
||||
.unwrap_or_else(|| generate_server_id(public_addr));
|
||||
let node_id = NodeId::new(node_id_str, public_addr);
|
||||
let config = ChitchatConfig {
|
||||
node_id,
|
||||
cluster_id: "testing".to_string(),
|
||||
gossip_interval: Duration::from_millis(opt.interval),
|
||||
listen_addr: opt.listen_addr,
|
||||
seed_nodes: opt.seeds.clone(),
|
||||
failure_detector_config: FailureDetectorConfig::default(),
|
||||
};
|
||||
let chitchat_handler = spawn_chitchat(config, Vec::new(), &UdpTransport).await?;
|
||||
let chitchat = chitchat_handler.chitchat();
|
||||
let api = Api { chitchat };
|
||||
let api_service = OpenApiService::new(api, "Hello World", "1.0")
|
||||
.server(&format!("http://{}/", opt.listen_addr));
|
||||
let docs = api_service.swagger_ui();
|
||||
let app = Route::new().nest("/", api_service).nest("/docs", docs);
|
||||
Server::new(TcpListener::bind(&opt.listen_addr))
|
||||
.run(app)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+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.1"
|
||||
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 =
|
||||
"n1qa4hswlcjmttulj0q9qa46jf64f93pecl6tydcsjldfe0hy5ju0sdmwzya";
|
||||
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://v2-env-validator.qa.nymte.ch/",
|
||||
Some("https://v2-env-val-api.qa.nymte.ch/api/"),
|
||||
"https://qwerty-validator.qa.nymte.ch/",
|
||||
Some("https://qwerty-validator-api.qa.nymte.ch/api"),
|
||||
)]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nymproject/nym-wallet-app",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.1",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -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.1"
|
||||
description = "Nym Native Wallet"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user