Compare commits
189 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c45e8da43d | |||
| 12cc49a734 | |||
| 7e56a9e88c | |||
| 9790009eac | |||
| 379d593daf | |||
| ce75b99b6f | |||
| bcb7c41fd7 | |||
| bb091ce47f | |||
| b28ff17c30 | |||
| 9b14e00653 | |||
| ec8b5e6e9d | |||
| effed4d7d6 | |||
| d4584c305a | |||
| afc53d4379 | |||
| 4278e88d3c | |||
| e12a34ce6b | |||
| 1de64f7b52 | |||
| 66dbe09e66 | |||
| dcce269921 | |||
| c043f0096a | |||
| a7cd7a58f2 | |||
| fe6da046dc | |||
| 8bbdb94b13 | |||
| e32601ab86 | |||
| 161138bdff | |||
| 0529e84a31 | |||
| 95f98016de | |||
| 4967bbb5bd | |||
| 2952144d32 | |||
| 80c21b3ed9 | |||
| 1f0d5f8ad0 | |||
| 49ce56c367 | |||
| 4ab6f4c3a9 | |||
| 3727370b9e | |||
| b3272097f9 | |||
| ebc13c4327 | |||
| ec3a6b3e27 | |||
| 19f3c76f72 | |||
| 90cc239999 | |||
| c1bd5db902 | |||
| fb1649bab5 | |||
| b21ca41e16 | |||
| 8656abcbde | |||
| 99b30c2570 | |||
| 2c5d31e685 | |||
| 3ae9ea5de6 | |||
| cf65bc1295 | |||
| 8bcec241a2 | |||
| 306e9b9dc2 | |||
| 2d5f851252 | |||
| d36e349cc6 | |||
| 4990a4745f | |||
| 5ce087dafe | |||
| caf03a09c8 | |||
| 0d399f7d70 | |||
| 56cf181770 | |||
| f0aa2feb76 | |||
| 4df927cc3d | |||
| 5db47b8931 | |||
| 27c1b29615 | |||
| c80c8ef899 | |||
| 3f4373eb98 | |||
| cf10bb12ef | |||
| cb1e93e58d | |||
| d0cd22c4da | |||
| a721e97c06 | |||
| f4f98027a0 | |||
| dee27e805d | |||
| 6f7dc36e5c | |||
| ef50f361ba | |||
| 3c55b28e69 | |||
| f1624e658e | |||
| fc44f2fe1c | |||
| cc26e4043c | |||
| bb242080cf | |||
| 3ebaf48aa3 | |||
| 2d7a55daba | |||
| 5f36742ce6 | |||
| 8547e770da | |||
| 862178c9c5 | |||
| 33a339ae2c | |||
| 5d583548ec | |||
| ba979c2e60 | |||
| dbb674f042 | |||
| c3bea668d5 | |||
| e0dd9b533e | |||
| 5ab3f95b8f | |||
| 46097c80fe | |||
| ab0eb35906 | |||
| 8bb3b066ba | |||
| 6a3ac6b9be | |||
| da95e4e903 | |||
| 732235afc0 | |||
| 27a81df79e | |||
| 03d654214f | |||
| a9dcd8e6c7 | |||
| a43d183b4f | |||
| 54d97fdbec | |||
| 69e5abaed9 | |||
| e3284f30a8 | |||
| 46d2d1f88b | |||
| 8f11b39e95 | |||
| 2192777485 | |||
| 11b8c52b30 | |||
| 99cfdab601 | |||
| 11a67adc04 | |||
| 65f75c5fe5 | |||
| 68c2cf5f95 | |||
| 1dd89ea1aa | |||
| 6593605834 | |||
| 9d4c62cad6 | |||
| f72a38a5a8 | |||
| cc641052b3 | |||
| 545c8b76a7 | |||
| be07e4997e | |||
| 139a0dca2f | |||
| e9c0b9bef3 | |||
| 9b28de4a06 | |||
| f0c50556ad | |||
| f50af85fb1 | |||
| 25f1fb2eb8 | |||
| 27ab849018 | |||
| 803f7117ea | |||
| 611d37e46f | |||
| 99b35f8d01 | |||
| f35bfc63e2 | |||
| 1be85dced6 | |||
| 7a3253e025 | |||
| 8a3351bf82 | |||
| 55e45a0d88 | |||
| 5a55c320cb | |||
| ba64c57283 | |||
| 739b2f88f9 | |||
| ce269e60e4 | |||
| ad9ea03683 | |||
| 47cae50e68 | |||
| 2a04234c26 | |||
| ef8f6ed07b | |||
| c644956576 | |||
| c329724f8c | |||
| a96383e714 | |||
| 7dc776f98a | |||
| 9717bcbb17 | |||
| 978cbc4f00 | |||
| ebb06d4beb | |||
| 1241a81514 | |||
| 08a190c1cb | |||
| 81f36e8da7 | |||
| f230229ce9 | |||
| 912fb4ab38 | |||
| 99ceabb0b0 | |||
| 25df7bcd4d | |||
| 1cdca7bec3 | |||
| c809c7733d | |||
| 7b53003edb | |||
| 831d9d2bf8 | |||
| cb7c51ba12 | |||
| 0310f0a8a9 | |||
| bb79d08f6d | |||
| 414c86b500 | |||
| 4304ffcf3c | |||
| 309b23e18a | |||
| 52703583f0 | |||
| 6473ef13c6 | |||
| 9a45f15ba4 | |||
| 746795b7ce | |||
| 8b81247044 | |||
| c6cd787950 | |||
| f9ab20b10f | |||
| acffd496ed | |||
| 466ac1a1e0 | |||
| d53adcd17e | |||
| 36e82e831f | |||
| cbe0115f01 | |||
| 1dae3c3fc2 | |||
| 574e5cf10a | |||
| b3fcbb6726 | |||
| f96a60b6a2 | |||
| d480ddb133 | |||
| b119820591 | |||
| e128949dc2 | |||
| 9499b987e5 | |||
| d6ac786295 | |||
| 4d09d9c3db | |||
| 8c9044adf3 | |||
| 472085ca52 | |||
| 2f089e80ff | |||
| f7b979825b | |||
| 6ac1259f7a |
@@ -3,3 +3,21 @@
|
||||
|
||||
RUST_LOG=info
|
||||
RUST_BACKTRACE=1
|
||||
|
||||
#########################################
|
||||
# geoipupdate (needed for explorer-api) #
|
||||
#########################################
|
||||
# MaxMind account ID (change it to a valid account ID)
|
||||
GEOIPUPDATE_ACCOUNT_ID=xxx
|
||||
# MaxMind license key (change it to a valid license key)
|
||||
GEOIPUPDATE_LICENSE_KEY=xxx
|
||||
# List of space-separated database edition IDs. Edition IDs may
|
||||
# consist of letters, digits, and dashes. For example, GeoIP2-City
|
||||
# would download the GeoIP2 City database (GeoIP2-City).
|
||||
GEOIPUPDATE_EDITION_IDS=GeoLite2-Country
|
||||
# The number of hours between geoipupdate runs. If this is not set
|
||||
# or is set to 0, geoipupdate will run once and exit.
|
||||
GEOIPUPDATE_FREQUENCY=72
|
||||
# The path to the directory where geoipupdate will download the
|
||||
# database.
|
||||
GEOIP_DB_DIRECTORY=./explorer-api/geo_ip
|
||||
|
||||
+27
-14
@@ -1,36 +1,49 @@
|
||||
name: Daily security audit
|
||||
|
||||
on: workflow_dispatch
|
||||
on:
|
||||
schedule:
|
||||
- cron: '5 9 * * *'
|
||||
jobs:
|
||||
security_audit:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/audit-check@v1
|
||||
- name: Checkout repository code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
toolchain: stable
|
||||
- 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
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: report
|
||||
path: .github/workflows/support-files/notifications/deny.message
|
||||
notification:
|
||||
if: ${{ failure() }}
|
||||
needs: security_audit
|
||||
needs: cargo-deny
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
- name: Download report from previous job
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: report
|
||||
path: .github/workflows/support-files/notifications
|
||||
- name: Keybase - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Keybase - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: nightly
|
||||
NYM_PROJECT_NAME: "Nym daily audit"
|
||||
NYM_NOTIFICATION_KIND: security
|
||||
NYM_PROJECT_NAME: "Daily security report"
|
||||
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_NYMBTECH_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "test"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "security"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
|
||||
@@ -3,7 +3,7 @@ require('dotenv').config();
|
||||
const Bot = require('keybase-bot');
|
||||
|
||||
let context = {
|
||||
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect'],
|
||||
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect','security'],
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
const Handlebars = require('handlebars');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { Octokit, App } = require('octokit');
|
||||
|
||||
async function addToContextAndValidate(context) {
|
||||
return
|
||||
}
|
||||
|
||||
async function getMessageBody(context) {
|
||||
try {
|
||||
const source = fs
|
||||
.readFileSync("deny.message").toString();
|
||||
return source;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
addToContextAndValidate,
|
||||
getMessageBody,
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
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
|
||||
@@ -10,16 +10,30 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- 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])
|
||||
|
||||
### 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])
|
||||
|
||||
[#1541]: https://github.com/nymtech/nym/pull/1541
|
||||
[#1558]: https://github.com/nymtech/nym/pull/1558
|
||||
@@ -28,6 +42,19 @@ 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
|
||||
[#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
|
||||
|
||||
|
||||
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
|
||||
|
||||
Generated
+151
-386
@@ -102,12 +102,6 @@ version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.2"
|
||||
@@ -283,28 +277,16 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848"
|
||||
dependencies = [
|
||||
"funty 1.1.0",
|
||||
"radium 0.6.2",
|
||||
"tap",
|
||||
"wyz 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b"
|
||||
dependencies = [
|
||||
"funty 2.0.0",
|
||||
"radium 0.7.0",
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz 0.5.0",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -326,7 +308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec 0.7.2",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"constant_time_eq",
|
||||
@@ -434,12 +416,6 @@ version = "3.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||
|
||||
[[package]]
|
||||
name = "byte-slice-cast"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e"
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
@@ -610,6 +586,7 @@ dependencies = [
|
||||
"futures",
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
"gloo-timers",
|
||||
"humantime-serde",
|
||||
"log",
|
||||
"nonexhaustive-delayqueue",
|
||||
@@ -626,6 +603,9 @@ dependencies = [
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-timer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -757,15 +737,12 @@ name = "contracts-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.16.0"
|
||||
@@ -1332,19 +1309,6 @@ dependencies = [
|
||||
"const-oid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version 0.4.0",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "devise"
|
||||
version = "0.3.1"
|
||||
@@ -1431,7 +1395,7 @@ dependencies = [
|
||||
name = "dkg"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitvec 1.0.0",
|
||||
"bitvec",
|
||||
"bls12_381 0.6.0",
|
||||
"bs58",
|
||||
"criterion",
|
||||
@@ -1602,49 +1566,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethabi"
|
||||
version = "14.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01317735d563b3bad2d5f90d2e1799f414165408251abb762510f40e790e69a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ethereum-types",
|
||||
"hex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha3",
|
||||
"thiserror",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethbloom"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb684ac8fa8f6c5759f788862bb22ec6fe3cb392f6bfd08e3c64b603661e3f8"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
"fixed-hash",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethereum-types"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f64b5df66a228d85e4b17e5d6c6aa43b0310898ffe8a85988c4c032357aaabfd"
|
||||
dependencies = [
|
||||
"ethbloom",
|
||||
"fixed-hash",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"primitive-types",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
@@ -1665,10 +1586,13 @@ version = "1.0.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 3.2.8",
|
||||
"contracts-common",
|
||||
"dotenv",
|
||||
"humantime-serde",
|
||||
"isocountry",
|
||||
"itertools",
|
||||
"log",
|
||||
"logging",
|
||||
"maxminddb",
|
||||
"mixnet-contract-common",
|
||||
"network-defaults",
|
||||
@@ -1746,18 +1670,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-hash"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rand 0.8.5",
|
||||
"rustc-hex",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.1"
|
||||
@@ -1798,21 +1710,6 @@ dependencies = [
|
||||
"spin 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluvio-wasm-timer"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b768c170dc045fa587a8f948c91f9bcfb87f774930477c6215addf54317f137f"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"js-sys",
|
||||
"parking_lot 0.11.2",
|
||||
"pin-utils",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -1866,12 +1763,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@@ -1960,12 +1851,6 @@ version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.24"
|
||||
@@ -2002,7 +1887,6 @@ dependencies = [
|
||||
"credential-storage",
|
||||
"credentials",
|
||||
"crypto",
|
||||
"fluvio-wasm-timer",
|
||||
"futures",
|
||||
"gateway-requests",
|
||||
"getrandom 0.2.6",
|
||||
@@ -2011,7 +1895,6 @@ dependencies = [
|
||||
"nymsphinx",
|
||||
"pemstore",
|
||||
"rand 0.7.3",
|
||||
"secp256k1",
|
||||
"task",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@@ -2022,8 +1905,8 @@ dependencies = [
|
||||
"validator-client",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-timer",
|
||||
"wasm-utils",
|
||||
"web3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2150,6 +2033,18 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.10.0"
|
||||
@@ -2537,44 +2432,6 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-codec"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-rlp"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808"
|
||||
dependencies = [
|
||||
"rlp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-serde"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-trait-for-tuples"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inclusion-probability"
|
||||
version = "0.1.0"
|
||||
@@ -2710,21 +2567,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpc-core"
|
||||
version = "18.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.10.4"
|
||||
@@ -2889,6 +2731,14 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "logging"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.5.4"
|
||||
@@ -2982,25 +2832,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.2"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9"
|
||||
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"miow",
|
||||
"ntapi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"windows-sys 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3021,6 +2860,7 @@ dependencies = [
|
||||
"bs58",
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"humantime-serde",
|
||||
"log",
|
||||
"rand_chacha 0.3.1",
|
||||
"schemars",
|
||||
@@ -3146,9 +2986,12 @@ dependencies = [
|
||||
name = "nonexhaustive-delayqueue"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util 0.7.3",
|
||||
"wasm-timer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3210,6 +3053,21 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-bity-integration"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cosmrs",
|
||||
"eyre",
|
||||
"k256",
|
||||
"nym-cli-commands",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"validator-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.0.0"
|
||||
@@ -3223,6 +3081,7 @@ dependencies = [
|
||||
"clap_complete_fig",
|
||||
"dotenv",
|
||||
"log",
|
||||
"logging",
|
||||
"network-defaults",
|
||||
"nym-cli-commands",
|
||||
"pretty_env_logger",
|
||||
@@ -3278,6 +3137,7 @@ dependencies = [
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
"log",
|
||||
"logging",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"pemstore",
|
||||
@@ -3287,6 +3147,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sled",
|
||||
"task",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.14.0",
|
||||
"topology",
|
||||
@@ -3319,6 +3180,7 @@ dependencies = [
|
||||
"gateway-requests",
|
||||
"humantime-serde",
|
||||
"log",
|
||||
"logging",
|
||||
"mixnet-client",
|
||||
"mixnode-common",
|
||||
"network-defaults",
|
||||
@@ -3361,6 +3223,7 @@ dependencies = [
|
||||
"humantime-serde",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"logging",
|
||||
"mixnet-client",
|
||||
"mixnode-common",
|
||||
"nonexhaustive-delayqueue",
|
||||
@@ -3395,6 +3258,7 @@ dependencies = [
|
||||
"futures",
|
||||
"ipnetwork 0.20.0",
|
||||
"log",
|
||||
"logging",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"ordered-buffer",
|
||||
@@ -3420,6 +3284,7 @@ version = "1.0.2"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"log",
|
||||
"logging",
|
||||
"pretty_env_logger",
|
||||
"rocket",
|
||||
"serde",
|
||||
@@ -3446,6 +3311,7 @@ dependencies = [
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
"log",
|
||||
"logging",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"ordered-buffer",
|
||||
@@ -3458,6 +3324,7 @@ dependencies = [
|
||||
"snafu 0.6.10",
|
||||
"socks5-requests",
|
||||
"task",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"topology",
|
||||
"url",
|
||||
@@ -3504,6 +3371,7 @@ dependencies = [
|
||||
"coconut-interface",
|
||||
"config",
|
||||
"console-subscriber",
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"credential-storage",
|
||||
"credentials",
|
||||
@@ -3518,6 +3386,7 @@ dependencies = [
|
||||
"humantime-serde",
|
||||
"inclusion-probability",
|
||||
"log",
|
||||
"logging",
|
||||
"mixnet-contract-common",
|
||||
"multisig-contract-common",
|
||||
"nymcoconut",
|
||||
@@ -3809,32 +3678,6 @@ dependencies = [
|
||||
"group 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.2",
|
||||
"bitvec 0.20.4",
|
||||
"byte-slice-cast",
|
||||
"impl-trait-for-tuples",
|
||||
"parity-scale-codec-derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec-derive"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
@@ -3880,7 +3723,7 @@ dependencies = [
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec 1.8.0",
|
||||
"windows-sys",
|
||||
"windows-sys 0.34.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4143,29 +3986,6 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primitive-types"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06345ee39fbccfb06ab45f3a1a5798d9dafa04cb8921a76d227040003a234b0e"
|
||||
dependencies = [
|
||||
"fixed-hash",
|
||||
"impl-codec",
|
||||
"impl-rlp",
|
||||
"impl-serde",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
@@ -4348,12 +4168,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
@@ -4730,16 +4544,6 @@ dependencies = [
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rlp"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"rustc-hex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocket"
|
||||
version = "0.5.0-rc.2"
|
||||
@@ -4866,12 +4670,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hex"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
@@ -5037,24 +4835,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a"
|
||||
dependencies = [
|
||||
"secp256k1-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1-sys"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.6.1"
|
||||
@@ -5441,21 +5221,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soketto"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4919971d141dbadaa0e82b5d369e2d7666c98e4625046140615ca363e50d4daa"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"futures",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"sha-1 0.9.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx"
|
||||
version = "0.1.0"
|
||||
@@ -6060,15 +5825,6 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
@@ -6081,16 +5837,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.19.1"
|
||||
version = "1.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95eec79ea28c00a365f539f1961e9278fbcaf81c0ff6aaf0e93c181352446948"
|
||||
checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.0",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
@@ -6211,7 +5967,6 @@ checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
@@ -6655,6 +6410,7 @@ dependencies = [
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"config",
|
||||
"contracts-common",
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"cw3",
|
||||
@@ -6743,12 +6499,14 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
name = "vesting-contract"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"vergen 5.1.17",
|
||||
"vesting-contract-common",
|
||||
]
|
||||
|
||||
@@ -6756,7 +6514,9 @@ dependencies = [
|
||||
name = "vesting-contract-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"contracts-common",
|
||||
"cosmwasm-std",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -6804,9 +6564,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.78"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
@@ -6814,13 +6574,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.78"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -6841,9 +6601,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.78"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -6851,9 +6611,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.78"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6864,9 +6624,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.78"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-timer"
|
||||
version = "0.2.5"
|
||||
source = "git+https://github.com/mmsinclair/wasm-timer?rev=b9d1a54ad514c2f230a026afe0dde341e98cd7b6#b9d1a54ad514c2f230a026afe0dde341e98cd7b6"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"js-sys",
|
||||
"parking_lot 0.11.2",
|
||||
"pin-utils",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-utils"
|
||||
@@ -6890,52 +6664,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web3"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd24abe6f2b68e0677f843059faea87bcbd4892e39f02886f366d8222c3c540d"
|
||||
dependencies = [
|
||||
"arrayvec 0.5.2",
|
||||
"base64",
|
||||
"bytes",
|
||||
"derive_more",
|
||||
"ethabi",
|
||||
"ethereum-types",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"headers",
|
||||
"hex",
|
||||
"jsonrpc-core",
|
||||
"log",
|
||||
"parking_lot 0.11.2",
|
||||
"pin-project",
|
||||
"reqwest",
|
||||
"rlp",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"soketto",
|
||||
"tiny-keccak",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util 0.6.9",
|
||||
"url",
|
||||
"web3-async-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web3-async-native-tls"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.21.4"
|
||||
@@ -7031,11 +6759,24 @@ version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_msvc 0.34.0",
|
||||
"windows_i686_gnu 0.34.0",
|
||||
"windows_i686_msvc 0.34.0",
|
||||
"windows_x86_64_gnu 0.34.0",
|
||||
"windows_x86_64_msvc 0.34.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc 0.36.1",
|
||||
"windows_i686_gnu 0.36.1",
|
||||
"windows_i686_msvc 0.36.1",
|
||||
"windows_x86_64_gnu 0.36.1",
|
||||
"windows_x86_64_msvc 0.36.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7044,30 +6785,60 @@ version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
@@ -7077,12 +6848,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.0"
|
||||
|
||||
@@ -41,6 +41,7 @@ members = [
|
||||
"common/execute",
|
||||
"common/inclusion-probability",
|
||||
"common/ledger",
|
||||
"common/logging",
|
||||
"common/mixnode-common",
|
||||
"common/network-defaults",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
@@ -67,6 +68,7 @@ members = [
|
||||
"explorer-api",
|
||||
"gateway",
|
||||
"gateway/gateway-requests",
|
||||
"integrations/bity",
|
||||
"mixnode",
|
||||
"service-providers/network-requester",
|
||||
"service-providers/network-statistics",
|
||||
|
||||
@@ -2,12 +2,12 @@ test: clippy-all cargo-test wasm fmt
|
||||
test-all: test cargo-test-expensive
|
||||
no-clippy: build cargo-test wasm fmt
|
||||
happy: fmt clippy-happy test
|
||||
clippy-all: clippy-main clippy-coconut clippy-all-contracts clippy-all-wallet clippy-all-connect
|
||||
clippy-all: clippy-main clippy-coconut clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-wasm-client
|
||||
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect
|
||||
cargo-test: test-main test-contracts test-wallet test-connect test-coconut
|
||||
cargo-test: test-main test-contracts test-wallet test-connect test-coconut test-wasm-client
|
||||
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive test-coconut-expensive
|
||||
build: build-contracts build-wallet build-main build-connect
|
||||
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect
|
||||
build: build-contracts build-wallet build-main build-connect build-wasm-client
|
||||
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-wasm-client
|
||||
|
||||
clippy-happy-main:
|
||||
cargo clippy
|
||||
@@ -40,6 +40,9 @@ clippy-all-wallet:
|
||||
clippy-all-connect:
|
||||
cargo clippy --workspace --manifest-path nym-connect/Cargo.toml --all-features -- -D warnings
|
||||
|
||||
clippy-all-wasm-client:
|
||||
cargo clippy --workspace --manifest-path clients/webassembly/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
|
||||
test-main:
|
||||
cargo test --workspace
|
||||
|
||||
@@ -68,6 +71,9 @@ test-wallet:
|
||||
test-wallet-expensive:
|
||||
cargo test --manifest-path nym-wallet/Cargo.toml --all-features -- --ignored
|
||||
|
||||
test-wasm-client:
|
||||
cargo test --workspace --manifest-path clients/webassembly/Cargo.toml --all-features
|
||||
|
||||
test-connect:
|
||||
cargo test --manifest-path nym-connect/Cargo.toml --all-features
|
||||
|
||||
@@ -86,6 +92,9 @@ build-wallet:
|
||||
build-connect:
|
||||
cargo build --manifest-path nym-connect/Cargo.toml --workspace
|
||||
|
||||
build-wasm-client:
|
||||
cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown
|
||||
|
||||
build-nym-cli:
|
||||
cargo build --release --manifest-path tools/nym-cli/Cargo.toml
|
||||
|
||||
@@ -101,9 +110,18 @@ fmt-wallet:
|
||||
fmt-connect:
|
||||
cargo fmt --manifest-path nym-connect/Cargo.toml --all
|
||||
|
||||
fmt-wasm-client:
|
||||
cargo fmt --manifest-path clients/webassembly/Cargo.toml --all
|
||||
|
||||
wasm:
|
||||
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
|
||||
|
||||
mixnet-opt: wasm
|
||||
cd contracts/mixnet && make opt
|
||||
|
||||
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
|
||||
@@ -13,26 +13,47 @@ humantime-serde = "1.0"
|
||||
log = "0.4"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sled = "0.34"
|
||||
sled = { version = "0.34", optional = true }
|
||||
thiserror = "1.0.34"
|
||||
tokio = { version = "1.19.1", features = ["macros"] }
|
||||
url = { version ="2.2", features = ["serde"] }
|
||||
|
||||
# internal
|
||||
config = { path = "../../common/config" }
|
||||
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"] }
|
||||
gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
nonexhaustive-delayqueue = { path = "../../common/nonexhaustive-delayqueue" }
|
||||
nymsphinx = { path = "../../common/nymsphinx" }
|
||||
pemstore = { path = "../../common/pemstore" }
|
||||
task = { path = "../../common/task" }
|
||||
topology = { path = "../../common/topology" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
|
||||
tap = "1.0.1"
|
||||
|
||||
tokio = { version = "1.21.2", features = ["time", "macros"]}
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
|
||||
version = "0.4"
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||
version = "0.2.83"
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-timer]
|
||||
git = "https://github.com/mmsinclair/wasm-timer"
|
||||
rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.gloo-timers]
|
||||
version = "0.2.4"
|
||||
features = ["futures"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.task]
|
||||
path = "../../common/task"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
default = ["reply-surb"]
|
||||
wasm = ["gateway-client/wasm"]
|
||||
coconut = ["gateway-client/coconut", "gateway-requests/coconut"]
|
||||
reply-surb = ["sled"]
|
||||
@@ -3,20 +3,27 @@
|
||||
|
||||
use crate::client::mix_traffic::BatchMixMessageSender;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use crate::spawn_future;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{Future, Stream, StreamExt};
|
||||
use log::*;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::cover::generate_loop_cover_packet;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use nymsphinx::utils::sample_poisson_duration;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use task::ShutdownListener;
|
||||
use tokio::task::JoinHandle;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::error::TrySendError;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_timer;
|
||||
|
||||
pub struct LoopCoverTrafficStream<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
@@ -25,18 +32,22 @@ where
|
||||
ack_key: Arc<AckKey>,
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: time::Duration,
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// Average delay a data packet is going to get delay at a single mixnode.
|
||||
average_packet_delay: time::Duration,
|
||||
average_packet_delay: Duration,
|
||||
|
||||
/// Average delay between sending subsequent cover packets.
|
||||
average_cover_message_sending_delay: time::Duration,
|
||||
average_cover_message_sending_delay: Duration,
|
||||
|
||||
/// Internal state, determined by `average_message_sending_delay`,
|
||||
/// used to keep track of when a next packet should be sent out.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
next_delay: Pin<Box<time::Sleep>>,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
next_delay: Pin<Box<wasm_timer::Delay>>,
|
||||
|
||||
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
|
||||
/// out to the network without any further delays.
|
||||
mix_tx: BatchMixMessageSender,
|
||||
@@ -50,8 +61,8 @@ where
|
||||
/// Accessor to the common instance of network topology.
|
||||
topology_access: TopologyAccessor,
|
||||
|
||||
/// Listen to shutdown signals.
|
||||
shutdown: ShutdownListener,
|
||||
/// Predefined packet size used for the loop cover messages.
|
||||
packet_size: PacketSize,
|
||||
}
|
||||
|
||||
impl<R> Stream for LoopCoverTrafficStream<R>
|
||||
@@ -73,13 +84,21 @@ where
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let avg_delay = self.average_cover_message_sending_delay;
|
||||
let now = self.next_delay.deadline();
|
||||
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
let next = now + next_poisson_delay;
|
||||
self.next_delay.as_mut().reset(next);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let now = self.next_delay.deadline();
|
||||
let next = now + next_poisson_delay;
|
||||
self.next_delay.as_mut().reset(next);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
self.next_delay.as_mut().reset(next_poisson_delay);
|
||||
}
|
||||
|
||||
Poll::Ready(Some(()))
|
||||
}
|
||||
@@ -91,30 +110,39 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
average_ack_delay: time::Duration,
|
||||
average_packet_delay: time::Duration,
|
||||
average_cover_message_sending_delay: time::Duration,
|
||||
average_ack_delay: Duration,
|
||||
average_packet_delay: Duration,
|
||||
average_cover_message_sending_delay: Duration,
|
||||
mix_tx: BatchMixMessageSender,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor,
|
||||
shutdown: ShutdownListener,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let next_delay = Box::pin(time::sleep(Default::default()));
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let next_delay = Box::pin(wasm_timer::Delay::new(Default::default()));
|
||||
|
||||
LoopCoverTrafficStream {
|
||||
ack_key,
|
||||
average_ack_delay,
|
||||
average_packet_delay,
|
||||
average_cover_message_sending_delay,
|
||||
next_delay: Box::pin(time::sleep(Default::default())),
|
||||
next_delay,
|
||||
mix_tx,
|
||||
our_full_destination,
|
||||
rng,
|
||||
topology_access,
|
||||
shutdown,
|
||||
packet_size: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_custom_packet_size(&mut self, packet_size: PacketSize) {
|
||||
self.packet_size = packet_size;
|
||||
}
|
||||
|
||||
async fn on_new_message(&mut self) {
|
||||
trace!("next cover message!");
|
||||
|
||||
@@ -140,14 +168,22 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
&self.our_full_destination,
|
||||
self.average_ack_delay,
|
||||
self.average_packet_delay,
|
||||
self.packet_size,
|
||||
)
|
||||
.expect("Somehow failed to generate a loop cover message with a valid topology");
|
||||
|
||||
// if this one fails, there's no retrying because it means that either:
|
||||
// - we run out of memory
|
||||
// - the receiver channel is closed
|
||||
// in either case there's no recovery and we can only panic
|
||||
self.mix_tx.unbounded_send(vec![cover_message]).unwrap();
|
||||
if let Err(err) = self.mix_tx.try_send(vec![cover_message]) {
|
||||
match err {
|
||||
TrySendError::Full(_) => {
|
||||
// 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");
|
||||
}
|
||||
TrySendError::Closed(_) => {
|
||||
log::warn!("Failed to send cover message - channel closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: I'm not entirely sure whether this is really required, because I'm not 100%
|
||||
// sure how `yield_now()` works - whether it just notifies the scheduler or whether it
|
||||
@@ -156,40 +192,56 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
|
||||
// JS: due to identical logical structure to OutQueueControl::on_message(), this is also
|
||||
// presumably required to prevent bugs in the future. Exact reason is still unknown to me.
|
||||
|
||||
// TODO: temporary and BAD workaround for wasm (we should find a way to yield here in wasm)
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
|
||||
// we should set initial delay only when we actually start the stream
|
||||
self.next_delay = Box::pin(time::sleep(sample_poisson_duration(
|
||||
&mut self.rng,
|
||||
self.average_cover_message_sending_delay,
|
||||
)));
|
||||
let sampled =
|
||||
sample_poisson_duration(&mut self.rng, self.average_cover_message_sending_delay);
|
||||
self.next_delay = Box::pin(time::sleep(sampled));
|
||||
|
||||
let mut shutdown = self.shutdown.clone();
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("LoopCoverTrafficStream: Received shutdown");
|
||||
}
|
||||
next = self.next() => {
|
||||
if next.is_some() {
|
||||
self.on_new_message().await;
|
||||
} else {
|
||||
log::trace!("LoopCoverTrafficStream: Stopping since channel closed");
|
||||
break;
|
||||
spawn_future(async move {
|
||||
debug!("Started LoopCoverTrafficStream with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("LoopCoverTrafficStream: Received shutdown");
|
||||
}
|
||||
next = self.next() => {
|
||||
if next.is_some() {
|
||||
self.on_new_message().await;
|
||||
} else {
|
||||
log::trace!("LoopCoverTrafficStream: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(self.shutdown.is_shutdown_poll());
|
||||
log::debug!("LoopCoverTrafficStream: Exiting");
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("LoopCoverTrafficStream: Exiting");
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
self.run().await;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn start(mut self) {
|
||||
// we should set initial delay only when we actually start the stream
|
||||
let sampled =
|
||||
sample_poisson_duration(&mut self.rng, self.average_cover_message_sending_delay);
|
||||
self.next_delay = Box::pin(wasm_timer::Delay::new(sampled));
|
||||
|
||||
spawn_future(async move {
|
||||
debug!("Started LoopCoverTrafficStream without graceful shutdown support");
|
||||
|
||||
while self.next().await.is_some() {
|
||||
self.on_new_message().await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use crate::spawn_future;
|
||||
use gateway_client::GatewayClient;
|
||||
use log::*;
|
||||
use nymsphinx::forwarding::packet::MixPacket;
|
||||
use task::ShutdownListener;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
|
||||
pub type BatchMixMessageReceiver = mpsc::UnboundedReceiver<Vec<MixPacket>>;
|
||||
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
|
||||
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
|
||||
|
||||
// We remind ourselves that 32 x 32kb = 1024kb, a reasonable size for a network buffer.
|
||||
pub const MIX_MESSAGE_RECEIVER_BUFFER_SIZE: usize = 32;
|
||||
const MAX_FAILURE_COUNT: usize = 100;
|
||||
|
||||
pub struct MixTrafficController {
|
||||
@@ -19,7 +18,6 @@ pub struct MixTrafficController {
|
||||
// later on gateway_client will need to be accessible by other entities
|
||||
gateway_client: GatewayClient,
|
||||
mix_rx: BatchMixMessageReceiver,
|
||||
shutdown: ShutdownListener,
|
||||
|
||||
// TODO: this is temporary work-around.
|
||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||
@@ -27,17 +25,17 @@ pub struct MixTrafficController {
|
||||
}
|
||||
|
||||
impl MixTrafficController {
|
||||
pub fn new(
|
||||
mix_rx: BatchMixMessageReceiver,
|
||||
gateway_client: GatewayClient,
|
||||
shutdown: ShutdownListener,
|
||||
) -> MixTrafficController {
|
||||
MixTrafficController {
|
||||
gateway_client,
|
||||
mix_rx,
|
||||
shutdown,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
}
|
||||
pub fn new(gateway_client: GatewayClient) -> (MixTrafficController, BatchMixMessageSender) {
|
||||
let (sphinx_message_sender, sphinx_message_receiver) =
|
||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||
(
|
||||
MixTrafficController {
|
||||
gateway_client,
|
||||
mix_rx: sphinx_message_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
},
|
||||
sphinx_message_sender,
|
||||
)
|
||||
}
|
||||
|
||||
async fn on_messages(&mut self, mut mix_packets: Vec<MixPacket>) {
|
||||
@@ -69,30 +67,40 @@ impl MixTrafficController {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
mix_packets = self.mix_rx.next() => match mix_packets {
|
||||
Some(mix_packets) => {
|
||||
self.on_messages(mix_packets).await;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
|
||||
spawn_future(async move {
|
||||
debug!("Started MixTrafficController with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
mix_packets = self.mix_rx.recv() => match mix_packets {
|
||||
Some(mix_packets) => {
|
||||
self.on_messages(mix_packets).await;
|
||||
},
|
||||
None => {
|
||||
log::trace!("MixTrafficController: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
log::trace!("MixTrafficController: Stopping since channel closed");
|
||||
break;
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("MixTrafficController: Received shutdown");
|
||||
}
|
||||
},
|
||||
_ = self.shutdown.recv() => {
|
||||
log::trace!("MixTrafficController: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(self.shutdown.is_shutdown_poll());
|
||||
log::debug!("MixTrafficController: Exiting");
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("MixTrafficController: Exiting");
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
self.run().await;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn start(mut self) {
|
||||
spawn_future(async move {
|
||||
debug!("Started MixTrafficController without graceful shutdown support");
|
||||
|
||||
while let Some(mix_packets) = self.mix_rx.recv().await {
|
||||
self.on_messages(mix_packets).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ pub mod key_manager;
|
||||
pub mod mix_traffic;
|
||||
pub mod real_messages_control;
|
||||
pub mod received_buffer;
|
||||
#[cfg(feature = "reply-surb")]
|
||||
pub mod reply_key_storage;
|
||||
pub mod topology_control;
|
||||
|
||||
|
||||
+24
-15
@@ -10,7 +10,6 @@ use nymsphinx::{
|
||||
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use task::ShutdownListener;
|
||||
|
||||
/// Module responsible for listening for any data resembling acknowledgements from the network
|
||||
/// and firing actions to remove them from the 'Pending' state.
|
||||
@@ -18,7 +17,6 @@ pub(super) struct AcknowledgementListener {
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: ActionSender,
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
impl AcknowledgementListener {
|
||||
@@ -26,13 +24,11 @@ impl AcknowledgementListener {
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: ActionSender,
|
||||
shutdown: ShutdownListener,
|
||||
) -> Self {
|
||||
AcknowledgementListener {
|
||||
ack_key,
|
||||
ack_receiver,
|
||||
action_sender,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,28 +63,41 @@ impl AcknowledgementListener {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started AcknowledgementListener");
|
||||
while !self.shutdown.is_shutdown() {
|
||||
async fn handle_ack_receiver_item(&mut self, item: Vec<Vec<u8>>) {
|
||||
// realistically we would only be getting one ack at the time
|
||||
for ack in item {
|
||||
self.on_ack(ack).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
debug!("Started AcknowledgementListener with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
acks = self.ack_receiver.next() => match acks {
|
||||
Some(acks) => {
|
||||
// realistically we would only be getting one ack at the time
|
||||
for ack in acks {
|
||||
self.on_ack(ack).await;
|
||||
}
|
||||
},
|
||||
Some(acks) => self.handle_ack_receiver_item(acks).await,
|
||||
None => {
|
||||
log::trace!("AcknowledgementListener: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.shutdown.recv() => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("AcknowledgementListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(self.shutdown.is_shutdown_poll());
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("AcknowledgementListener: Exiting");
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started AcknowledgementListener without graceful shutdown support");
|
||||
|
||||
while let Some(acks) = self.ack_receiver.next().await {
|
||||
self.handle_ack_receiver_item(acks).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-10
@@ -12,7 +12,6 @@ use nymsphinx::Delay as SphinxDelay;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use task::ShutdownListener;
|
||||
|
||||
pub(crate) type ActionSender = UnboundedSender<Action>;
|
||||
|
||||
@@ -100,16 +99,12 @@ pub(super) struct ActionController {
|
||||
|
||||
/// Channel for notifying `RetransmissionRequestListener` about expired acknowledgements.
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
|
||||
/// Listen for shutdown notifications
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
impl ActionController {
|
||||
pub(super) fn new(
|
||||
config: Config,
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
shutdown: ShutdownListener,
|
||||
) -> (Self, ActionSender) {
|
||||
let (sender, receiver) = mpsc::unbounded();
|
||||
(
|
||||
@@ -119,7 +114,6 @@ impl ActionController {
|
||||
pending_acks_timers: NonExhaustiveDelayQueue::new(),
|
||||
incoming_actions: receiver,
|
||||
retransmission_sender,
|
||||
shutdown,
|
||||
},
|
||||
sender,
|
||||
)
|
||||
@@ -251,8 +245,11 @@ impl ActionController {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
while !self.shutdown.is_shutdown() {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
debug!("Started ActionController with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
action = self.incoming_actions.next() => match action {
|
||||
Some(action) => self.process_action(action),
|
||||
@@ -270,12 +267,24 @@ impl ActionController {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.shutdown.recv() => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("ActionController: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(self.shutdown.is_shutdown_poll());
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("ActionController: Exiting");
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started ActionController without graceful shutdown support");
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
action = self.incoming_actions.next() => self.process_action(action.unwrap()),
|
||||
expired_ack = self.pending_acks_timers.next() => self.handle_expired_ack_timer(expired_ack.unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-11
@@ -3,7 +3,6 @@
|
||||
|
||||
use super::action_controller::{Action, ActionSender};
|
||||
use super::PendingAcknowledgement;
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
use crate::client::{
|
||||
inbound_messages::{InputMessage, InputMessageReceiver},
|
||||
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
|
||||
@@ -16,7 +15,9 @@ use nymsphinx::preparer::MessagePreparer;
|
||||
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
use task::ShutdownListener;
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
|
||||
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
|
||||
/// putting everything into sphinx packets, etc.
|
||||
@@ -32,8 +33,8 @@ where
|
||||
action_sender: ActionSender,
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
impl<R> InputMessageListener<R>
|
||||
@@ -51,8 +52,7 @@ where
|
||||
action_sender: ActionSender,
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
shutdown: ShutdownListener,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
) -> Self {
|
||||
InputMessageListener {
|
||||
ack_key,
|
||||
@@ -62,8 +62,8 @@ where
|
||||
action_sender,
|
||||
real_message_sender,
|
||||
topology_access,
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,12 +121,16 @@ where
|
||||
.prepare_and_split_message(content, with_reply_surb, topology)
|
||||
.expect("somehow the topology was invalid after all!");
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
if let Some(reply_key) = reply_key {
|
||||
self.reply_key_storage
|
||||
.insert_encryption_key(reply_key)
|
||||
.expect("Failed to insert surb reply key to the store!")
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "reply-surb"))]
|
||||
let _reply_key = reply_key;
|
||||
|
||||
// encrypt chunks, put them inside sphinx packets and generate acks
|
||||
let mut pending_acks = Vec::with_capacity(split_message.len());
|
||||
let mut real_messages = Vec::with_capacity(split_message.len());
|
||||
@@ -184,9 +188,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started InputMessageListener");
|
||||
while !self.shutdown.is_shutdown() {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
debug!("Started InputMessageListener with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
input_msg = self.input_receiver.next() => match input_msg {
|
||||
Some(input_msg) => {
|
||||
@@ -197,12 +203,20 @@ where
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.shutdown.recv() => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("InputMessageListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(self.shutdown.is_shutdown_poll());
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("InputMessageListener: Exiting");
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started InputMessageListener without graceful shutdown support");
|
||||
while let Some(input_msg) = self.input_receiver.next().await {
|
||||
self.on_input_message(input_msg).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+98
-68
@@ -8,11 +8,12 @@ use self::{
|
||||
sent_notification_listener::SentNotificationListener,
|
||||
};
|
||||
use super::real_traffic_stream::BatchRealMessageSender;
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
use crate::client::{inbound_messages::InputMessageReceiver, topology_control::TopologyAccessor};
|
||||
use crate::spawn_future;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::AcknowledgementReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use nymsphinx::{
|
||||
acknowledgements::AckKey,
|
||||
addressing::clients::Recipient,
|
||||
@@ -25,8 +26,9 @@ use std::{
|
||||
sync::{Arc, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
use task::ShutdownListener;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
|
||||
mod acknowledgement_listener;
|
||||
mod action_controller;
|
||||
@@ -120,6 +122,9 @@ pub(super) struct Config {
|
||||
|
||||
/// Average delay a data packet is going to get delayed at a single mixnode.
|
||||
average_packet_delay: Duration,
|
||||
|
||||
/// Predefined packet size used for the encapsulated messages.
|
||||
packet_size: PacketSize,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -134,19 +139,25 @@ impl Config {
|
||||
ack_wait_multiplier,
|
||||
average_ack_delay,
|
||||
average_packet_delay,
|
||||
packet_size: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_custom_packet_size(mut self, packet_size: PacketSize) -> Self {
|
||||
self.packet_size = packet_size;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct AcknowledgementController<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
acknowledgement_listener: Option<AcknowledgementListener>,
|
||||
input_message_listener: Option<InputMessageListener<R>>,
|
||||
retransmission_request_listener: Option<RetransmissionRequestListener<R>>,
|
||||
sent_notification_listener: Option<SentNotificationListener>,
|
||||
action_controller: Option<ActionController>,
|
||||
acknowledgement_listener: AcknowledgementListener,
|
||||
input_message_listener: InputMessageListener<R>,
|
||||
retransmission_request_listener: RetransmissionRequestListener<R>,
|
||||
sent_notification_listener: SentNotificationListener,
|
||||
action_controller: ActionController,
|
||||
}
|
||||
|
||||
impl<R> AcknowledgementController<R>
|
||||
@@ -160,30 +171,29 @@ where
|
||||
topology_access: TopologyAccessor,
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_recipient: Recipient,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
connectors: AcknowledgementControllerConnectors,
|
||||
shutdown: ShutdownListener,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
) -> Self {
|
||||
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
||||
|
||||
let action_config =
|
||||
action_controller::Config::new(config.ack_wait_addition, config.ack_wait_multiplier);
|
||||
let (action_controller, action_sender) =
|
||||
ActionController::new(action_config, retransmission_tx, shutdown.clone());
|
||||
ActionController::new(action_config, retransmission_tx);
|
||||
|
||||
let message_preparer = MessagePreparer::new(
|
||||
rng,
|
||||
ack_recipient,
|
||||
config.average_packet_delay,
|
||||
config.average_ack_delay,
|
||||
);
|
||||
)
|
||||
.with_custom_real_message_packet_size(config.packet_size);
|
||||
|
||||
// will listen for any acks coming from the network
|
||||
let acknowledgement_listener = AcknowledgementListener::new(
|
||||
Arc::clone(&ack_key),
|
||||
connectors.ack_receiver,
|
||||
action_sender.clone(),
|
||||
shutdown.clone(),
|
||||
);
|
||||
|
||||
// will listen for any new messages from the client
|
||||
@@ -195,8 +205,8 @@ where
|
||||
action_sender.clone(),
|
||||
connectors.real_message_sender.clone(),
|
||||
topology_access.clone(),
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
shutdown.clone(),
|
||||
);
|
||||
|
||||
// will listen for any ack timeouts and trigger retransmission
|
||||
@@ -208,75 +218,95 @@ where
|
||||
connectors.real_message_sender,
|
||||
retransmission_rx,
|
||||
topology_access,
|
||||
shutdown.clone(),
|
||||
);
|
||||
|
||||
// will listen for events indicating the packet was sent through the network so that
|
||||
// the retransmission timer should be started.
|
||||
let sent_notification_listener =
|
||||
SentNotificationListener::new(connectors.sent_notifier, action_sender, shutdown);
|
||||
SentNotificationListener::new(connectors.sent_notifier, action_sender);
|
||||
|
||||
AcknowledgementController {
|
||||
acknowledgement_listener: Some(acknowledgement_listener),
|
||||
input_message_listener: Some(input_message_listener),
|
||||
retransmission_request_listener: Some(retransmission_request_listener),
|
||||
sent_notification_listener: Some(sent_notification_listener),
|
||||
action_controller: Some(action_controller),
|
||||
acknowledgement_listener,
|
||||
input_message_listener,
|
||||
retransmission_request_listener,
|
||||
sent_notification_listener,
|
||||
action_controller,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
let mut acknowledgement_listener = self.acknowledgement_listener.take().unwrap();
|
||||
let mut input_message_listener = self.input_message_listener.take().unwrap();
|
||||
let mut retransmission_request_listener =
|
||||
self.retransmission_request_listener.take().unwrap();
|
||||
let mut sent_notification_listener = self.sent_notification_listener.take().unwrap();
|
||||
let mut action_controller = self.action_controller.take().unwrap();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
|
||||
let mut acknowledgement_listener = self.acknowledgement_listener;
|
||||
let mut input_message_listener = self.input_message_listener;
|
||||
let mut retransmission_request_listener = self.retransmission_request_listener;
|
||||
let mut sent_notification_listener = self.sent_notification_listener;
|
||||
let mut action_controller = self.action_controller;
|
||||
|
||||
// the below are log messages are errors as at the current stage we do not expect any of
|
||||
// the task to ever finish. This will of course change once we introduce
|
||||
// graceful shutdowns.
|
||||
let ack_listener_fut = tokio::spawn(async move {
|
||||
acknowledgement_listener.run().await;
|
||||
debug!("The acknowledgement listener has finished execution!");
|
||||
let shutdown_handle = shutdown.clone();
|
||||
spawn_future(async move {
|
||||
acknowledgement_listener
|
||||
});
|
||||
let input_listener_fut = tokio::spawn(async move {
|
||||
input_message_listener.run().await;
|
||||
debug!("The input listener has finished execution!");
|
||||
input_message_listener
|
||||
});
|
||||
let retransmission_req_fut = tokio::spawn(async move {
|
||||
retransmission_request_listener.run().await;
|
||||
debug!("The retransmission request listener has finished execution!");
|
||||
retransmission_request_listener
|
||||
});
|
||||
let sent_notification_fut = tokio::spawn(async move {
|
||||
sent_notification_listener.run().await;
|
||||
debug!("The sent notification listener has finished execution!");
|
||||
sent_notification_listener
|
||||
});
|
||||
let action_controller_fut = tokio::spawn(async move {
|
||||
action_controller.run().await;
|
||||
debug!("The controller has finished execution!");
|
||||
action_controller
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
debug!("The acknowledgement listener has finished execution!");
|
||||
});
|
||||
|
||||
// technically we don't have to bring `AcknowledgementController` back to a valid state
|
||||
// but we can do it, so why not? Perhaps it might be useful if we wanted to allow
|
||||
// for restarts of certain modules without killing the entire process.
|
||||
self.acknowledgement_listener = Some(ack_listener_fut.await.unwrap());
|
||||
self.input_message_listener = Some(input_listener_fut.await.unwrap());
|
||||
self.retransmission_request_listener = Some(retransmission_req_fut.await.unwrap());
|
||||
self.sent_notification_listener = Some(sent_notification_fut.await.unwrap());
|
||||
self.action_controller = Some(action_controller_fut.await.unwrap());
|
||||
let shutdown_handle = shutdown.clone();
|
||||
spawn_future(async move {
|
||||
input_message_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
debug!("The input listener has finished execution!");
|
||||
});
|
||||
|
||||
let shutdown_handle = shutdown.clone();
|
||||
spawn_future(async move {
|
||||
retransmission_request_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
debug!("The retransmission request listener has finished execution!");
|
||||
});
|
||||
|
||||
let shutdown_handle = shutdown.clone();
|
||||
spawn_future(async move {
|
||||
sent_notification_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
debug!("The sent notification listener has finished execution!");
|
||||
});
|
||||
|
||||
spawn_future(async move {
|
||||
action_controller.run_with_shutdown(shutdown).await;
|
||||
debug!("The controller has finished execution!");
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn start(mut self) -> JoinHandle<Self> {
|
||||
tokio::spawn(async move {
|
||||
self.run().await;
|
||||
self
|
||||
})
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) fn start(self) {
|
||||
let mut acknowledgement_listener = self.acknowledgement_listener;
|
||||
let mut input_message_listener = self.input_message_listener;
|
||||
let mut retransmission_request_listener = self.retransmission_request_listener;
|
||||
let mut sent_notification_listener = self.sent_notification_listener;
|
||||
let mut action_controller = self.action_controller;
|
||||
|
||||
spawn_future(async move {
|
||||
acknowledgement_listener.run().await;
|
||||
error!("The acknowledgement listener has finished execution!");
|
||||
});
|
||||
spawn_future(async move {
|
||||
input_message_listener.run().await;
|
||||
error!("The input listener has finished execution!");
|
||||
});
|
||||
spawn_future(async move {
|
||||
retransmission_request_listener.run().await;
|
||||
error!("The retransmission request listener has finished execution!");
|
||||
});
|
||||
spawn_future(async move {
|
||||
sent_notification_listener.run().await;
|
||||
error!("The sent notification listener has finished execution!");
|
||||
});
|
||||
spawn_future(async move {
|
||||
action_controller.run().await;
|
||||
error!("The controller has finished execution!");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+15
-9
@@ -14,7 +14,6 @@ use nymsphinx::preparer::MessagePreparer;
|
||||
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::{Arc, Weak};
|
||||
use task::ShutdownListener;
|
||||
|
||||
// responsible for packet retransmission upon fired timer
|
||||
pub(super) struct RetransmissionRequestListener<R>
|
||||
@@ -28,7 +27,6 @@ where
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
topology_access: TopologyAccessor,
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
impl<R> RetransmissionRequestListener<R>
|
||||
@@ -44,7 +42,6 @@ where
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
topology_access: TopologyAccessor,
|
||||
shutdown: ShutdownListener,
|
||||
) -> Self {
|
||||
RetransmissionRequestListener {
|
||||
ack_key,
|
||||
@@ -54,7 +51,6 @@ where
|
||||
real_message_sender,
|
||||
request_receiver,
|
||||
topology_access,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,10 +120,11 @@ where
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started RetransmissionRequestListener");
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
debug!("Started RetransmissionRequestListener with graceful shutdown support");
|
||||
|
||||
while !self.shutdown.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
|
||||
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack).await,
|
||||
@@ -136,12 +133,21 @@ where
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.shutdown.recv() => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("RetransmissionRequestListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(self.shutdown.is_shutdown_poll());
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("RetransmissionRequestListener: Exiting");
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started RetransmissionRequestListener without graceful shutdown support");
|
||||
|
||||
while let Some(timed_out_ack) = self.request_receiver.next().await {
|
||||
self.on_retransmission_request(timed_out_ack).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-9
@@ -6,7 +6,6 @@ use super::SentPacketNotificationReceiver;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
||||
use task::ShutdownListener;
|
||||
|
||||
/// Module responsible for starting up retransmission timers.
|
||||
/// It is required because when we send our packet to the `real traffic stream` controlled
|
||||
@@ -15,19 +14,16 @@ use task::ShutdownListener;
|
||||
pub(super) struct SentNotificationListener {
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
action_sender: ActionSender,
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
impl SentNotificationListener {
|
||||
pub(super) fn new(
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
action_sender: ActionSender,
|
||||
shutdown: ShutdownListener,
|
||||
) -> Self {
|
||||
SentNotificationListener {
|
||||
sent_notifier,
|
||||
action_sender,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +42,11 @@ impl SentNotificationListener {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started SentNotificationListener");
|
||||
while !self.shutdown.is_shutdown() {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
debug!("Started SentNotificationListener with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
frag_id = self.sent_notifier.next() => match frag_id {
|
||||
Some(frag_id) => {
|
||||
@@ -59,12 +57,21 @@ impl SentNotificationListener {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.shutdown.recv() => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("SentNotificationListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(self.shutdown.is_shutdown_poll());
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("SentNotificationListener: Exiting");
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started SentNotificationListener without graceful shutdown support");
|
||||
|
||||
while let Some(frag_id) = self.sent_notifier.next().await {
|
||||
self.on_sent_message(frag_id).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,21 +9,23 @@ use self::{
|
||||
acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
|
||||
};
|
||||
use crate::client::real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors;
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
use crate::client::{
|
||||
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use crate::spawn_future;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::AcknowledgementReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use task::ShutdownListener;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
|
||||
mod acknowledgement_control;
|
||||
mod real_traffic_stream;
|
||||
@@ -50,9 +52,18 @@ pub struct Config {
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delayed at a single mixnode.
|
||||
average_ack_delay_duration: Duration,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Predefined packet size used for the encapsulated messages.
|
||||
packet_size: PacketSize,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// TODO: change the config into a builder
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_wait_multiplier: f64,
|
||||
@@ -60,6 +71,7 @@ impl Config {
|
||||
average_ack_delay_duration: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
average_packet_delay_duration: Duration,
|
||||
disable_main_poisson_packet_distribution: bool,
|
||||
self_recipient: Recipient,
|
||||
) -> Self {
|
||||
Config {
|
||||
@@ -70,16 +82,22 @@ impl Config {
|
||||
average_message_sending_delay,
|
||||
average_packet_delay_duration,
|
||||
average_ack_delay_duration,
|
||||
disable_main_poisson_packet_distribution,
|
||||
packet_size: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_custom_packet_size(&mut self, packet_size: PacketSize) {
|
||||
self.packet_size = packet_size;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RealMessagesController<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
out_queue_control: Option<OutQueueControl<R>>,
|
||||
ack_control: Option<AcknowledgementController<R>>,
|
||||
out_queue_control: OutQueueControl<R>,
|
||||
ack_control: AcknowledgementController<R>,
|
||||
}
|
||||
|
||||
// obviously when we finally make shared rng that is on 'higher' level, this should become
|
||||
@@ -91,8 +109,7 @@ impl RealMessagesController<OsRng> {
|
||||
input_receiver: InputMessageReceiver,
|
||||
mix_sender: BatchMixMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
shutdown: ShutdownListener,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
@@ -111,7 +128,8 @@ impl RealMessagesController<OsRng> {
|
||||
config.ack_wait_multiplier,
|
||||
config.average_ack_delay_duration,
|
||||
config.average_packet_delay_duration,
|
||||
);
|
||||
)
|
||||
.with_custom_packet_size(config.packet_size);
|
||||
|
||||
let ack_control = AcknowledgementController::new(
|
||||
ack_control_config,
|
||||
@@ -119,16 +137,18 @@ impl RealMessagesController<OsRng> {
|
||||
topology_access.clone(),
|
||||
Arc::clone(&config.ack_key),
|
||||
config.self_recipient,
|
||||
reply_key_storage,
|
||||
ack_controller_connectors,
|
||||
shutdown.clone(),
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
);
|
||||
|
||||
let out_queue_config = real_traffic_stream::Config::new(
|
||||
config.average_ack_delay_duration,
|
||||
config.average_packet_delay_duration,
|
||||
config.average_message_sending_delay,
|
||||
);
|
||||
config.disable_main_poisson_packet_distribution,
|
||||
)
|
||||
.with_custom_cover_packet_size(config.packet_size);
|
||||
|
||||
let out_queue_control = OutQueueControl::new(
|
||||
out_queue_config,
|
||||
@@ -139,44 +159,36 @@ impl RealMessagesController<OsRng> {
|
||||
rng,
|
||||
config.self_recipient,
|
||||
topology_access,
|
||||
shutdown,
|
||||
);
|
||||
|
||||
RealMessagesController {
|
||||
out_queue_control: Some(out_queue_control),
|
||||
ack_control: Some(ack_control),
|
||||
out_queue_control,
|
||||
ack_control,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
let mut out_queue_control = self.out_queue_control.take().unwrap();
|
||||
let mut ack_control = self.ack_control.take().unwrap();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
|
||||
let mut out_queue_control = self.out_queue_control;
|
||||
let ack_control = self.ack_control;
|
||||
|
||||
// the below are log messages are errors as at the current stage we do not expect any of
|
||||
// the task to ever finish. This will of course change once we introduce
|
||||
// graceful shutdowns.
|
||||
let out_queue_control_fut = tokio::spawn(async move {
|
||||
out_queue_control.run_out_queue_control().await;
|
||||
let shutdown_handle = shutdown.clone();
|
||||
spawn_future(async move {
|
||||
out_queue_control.run_with_shutdown(shutdown_handle).await;
|
||||
debug!("The out queue controller has finished execution!");
|
||||
out_queue_control
|
||||
});
|
||||
let ack_control_fut = tokio::spawn(async move {
|
||||
ack_control.run().await;
|
||||
debug!("The acknowledgement controller has finished execution!");
|
||||
ack_control
|
||||
});
|
||||
|
||||
// technically we don't have to bring `RealMessagesController` back to a valid state
|
||||
// but we can do it, so why not? Perhaps it might be useful if we wanted to allow
|
||||
// for restarts of certain modules without killing the entire process.
|
||||
self.out_queue_control = Some(out_queue_control_fut.await.unwrap());
|
||||
self.ack_control = Some(ack_control_fut.await.unwrap());
|
||||
ack_control.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
pub fn start(mut self) -> JoinHandle<Self> {
|
||||
tokio::spawn(async move {
|
||||
self.run().await;
|
||||
self
|
||||
})
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn start(self) {
|
||||
let mut out_queue_control = self.out_queue_control;
|
||||
let ack_control = self.ack_control;
|
||||
|
||||
spawn_future(async move {
|
||||
out_queue_control.run().await;
|
||||
debug!("The out queue controller has finished execution!");
|
||||
});
|
||||
ack_control.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,37 @@ use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nymsphinx::cover::generate_loop_cover_packet;
|
||||
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;
|
||||
use task::ShutdownListener;
|
||||
|
||||
#[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;
|
||||
|
||||
/// Configurable parameters of the `OutQueueControl`
|
||||
pub(crate) struct Config {
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
@@ -32,6 +54,13 @@ pub(crate) struct Config {
|
||||
|
||||
/// Average delay between sending subsequent packets.
|
||||
average_message_sending_delay: Duration,
|
||||
|
||||
/// Controls whether the stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
disable_poisson_packet_distribution: bool,
|
||||
|
||||
/// Predefined packet size used for the loop cover messages.
|
||||
cover_packet_size: PacketSize,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -39,13 +68,116 @@ impl Config {
|
||||
average_ack_delay: Duration,
|
||||
average_packet_delay: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
disable_poisson_packet_distribution: bool,
|
||||
) -> Self {
|
||||
Config {
|
||||
average_ack_delay,
|
||||
average_packet_delay,
|
||||
average_message_sending_delay,
|
||||
disable_poisson_packet_distribution,
|
||||
cover_packet_size: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_custom_cover_packet_size(mut self, packet_size: PacketSize) -> Self {
|
||||
self.cover_packet_size = packet_size;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -63,7 +195,15 @@ where
|
||||
|
||||
/// Internal state, determined by `average_message_sending_delay`,
|
||||
/// used to keep track of when a next packet should be sent out.
|
||||
next_delay: Pin<Box<time::Sleep>>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
next_delay: Option<Pin<Box<time::Sleep>>>,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
next_delay: Option<Pin<Box<wasm_timer::Delay>>>,
|
||||
|
||||
// To make sure we don't overload the mix_tx channel, we limit the rate we are pushing
|
||||
// messages.
|
||||
sending_rate_controller: SendingDelayController,
|
||||
|
||||
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
|
||||
/// out to the network without any further delays.
|
||||
@@ -84,9 +224,6 @@ where
|
||||
|
||||
/// Buffer containing all real messages received. It is first exhausted before more are pulled.
|
||||
received_buffer: VecDeque<RealMessage>,
|
||||
|
||||
/// Listens for shutdown signals
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
pub(crate) struct RealMessage {
|
||||
@@ -113,55 +250,6 @@ pub(crate) enum StreamMessage {
|
||||
Real(Box<RealMessage>),
|
||||
}
|
||||
|
||||
impl<R> Stream for OutQueueControl<R>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
{
|
||||
type Item = StreamMessage;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// it is not yet time to return a message
|
||||
if self.next_delay.as_mut().poll(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let avg_delay = self.config.average_message_sending_delay;
|
||||
let now = self.next_delay.deadline();
|
||||
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
let next = now + next_poisson_delay;
|
||||
self.next_delay.as_mut().reset(next);
|
||||
|
||||
// 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
|
||||
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(),
|
||||
))))
|
||||
}
|
||||
|
||||
// otherwise construct a dummy one
|
||||
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> OutQueueControl<R>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
@@ -178,20 +266,22 @@ where
|
||||
rng: R,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor,
|
||||
shutdown: ShutdownListener,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
config,
|
||||
ack_key,
|
||||
sent_notifier,
|
||||
next_delay: Box::pin(time::sleep(Default::default())),
|
||||
next_delay: None,
|
||||
sending_rate_controller: SendingDelayController::new(
|
||||
MIN_DELAY_MULTIPLIER,
|
||||
MAX_DELAY_MULTIPLIER,
|
||||
),
|
||||
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
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +296,7 @@ where
|
||||
async fn on_message(&mut self, next_message: StreamMessage) {
|
||||
trace!("created new message");
|
||||
|
||||
let next_message = match next_message {
|
||||
let (next_message, fragment_id) = match next_message {
|
||||
StreamMessage::Cover => {
|
||||
// TODO for way down the line: in very rare cases (during topology update) we might have
|
||||
// to wait a really tiny bit before actually obtaining the permit hence messing with our
|
||||
@@ -225,34 +315,35 @@ where
|
||||
}
|
||||
let topology_ref = topology_ref_option.unwrap();
|
||||
|
||||
generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
topology_ref,
|
||||
&self.ack_key,
|
||||
&self.our_full_destination,
|
||||
self.config.average_ack_delay,
|
||||
self.config.average_packet_delay,
|
||||
(
|
||||
generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
topology_ref,
|
||||
&self.ack_key,
|
||||
&self.our_full_destination,
|
||||
self.config.average_ack_delay,
|
||||
self.config.average_packet_delay,
|
||||
self.config.cover_packet_size,
|
||||
)
|
||||
.expect(
|
||||
"Somehow failed to generate a loop cover message with a valid topology",
|
||||
),
|
||||
None,
|
||||
)
|
||||
.expect("Somehow failed to generate a loop cover message with a valid topology")
|
||||
}
|
||||
StreamMessage::Real(real_message) => {
|
||||
self.sent_notify(real_message.fragment_id);
|
||||
real_message.mix_packet
|
||||
(real_message.mix_packet, Some(real_message.fragment_id))
|
||||
}
|
||||
};
|
||||
|
||||
// if this one fails, there's no retrying because it means that either:
|
||||
// - we run out of memory
|
||||
// - the receiver channel is closed
|
||||
// in either case there's no recovery and we can only panic
|
||||
if let Err(err) = self.mix_tx.unbounded_send(vec![next_message]) {
|
||||
if self.shutdown.is_shutdown_poll() {
|
||||
log::info!("Failed to send (shutdown detected)");
|
||||
} else {
|
||||
// We don't try to limp along, panic to avoid continuing in a potentially
|
||||
// inconsistent state
|
||||
panic!("{err}");
|
||||
}
|
||||
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
|
||||
log::error!("Failed to send - channel closed: {}", err);
|
||||
}
|
||||
|
||||
// notify ack controller about sending our message only after we actually managed to push it
|
||||
// through the channel
|
||||
if let Some(fragment_id) = fragment_id {
|
||||
self.sent_notify(fragment_id);
|
||||
}
|
||||
|
||||
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
|
||||
@@ -260,18 +351,161 @@ where
|
||||
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
|
||||
// ready and hence was immediately re-scheduled causing other tasks to be starved;
|
||||
// yield makes it go back the scheduling queue regardless of its value availability
|
||||
|
||||
// TODO: temporary and BAD workaround for wasm (we should find a way to yield here in wasm)
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
// Send messages at certain rate and if no real traffic is available, send cover message.
|
||||
async fn run_normal_out_queue(&mut self) {
|
||||
// we should set initial delay only when we actually start the stream
|
||||
self.next_delay = Box::pin(time::sleep(sample_poisson_duration(
|
||||
&mut self.rng,
|
||||
self.config.average_message_sending_delay,
|
||||
)));
|
||||
fn current_average_message_sending_delay(&self) -> Duration {
|
||||
self.config.average_message_sending_delay
|
||||
* self.sending_rate_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()
|
||||
);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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_rate_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();
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_poisson(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
|
||||
// The average delay could change depending on if backpressure in the downstream channel
|
||||
// (mix_tx) was detected.
|
||||
self.adjust_current_average_message_sending_delay();
|
||||
let avg_delay = self.current_average_message_sending_delay();
|
||||
|
||||
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() {
|
||||
return Poll::Pending;
|
||||
};
|
||||
|
||||
// we know it's time to send a message, so let's prepare delay for the next one
|
||||
// Get the `now` by looking at the current `delay` deadline
|
||||
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
|
||||
|
||||
// The next interval value is `next_poisson_delay` after the one that just
|
||||
// yielded.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let now = next_delay.deadline();
|
||||
let next = now + next_poisson_delay;
|
||||
next_delay.as_mut().reset(next);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
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
|
||||
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(),
|
||||
))))
|
||||
}
|
||||
|
||||
// otherwise construct a dummy one
|
||||
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
|
||||
}
|
||||
} else {
|
||||
// we never set an initial delay - let's do it now
|
||||
cx.waker().wake_by_ref();
|
||||
|
||||
let sampled =
|
||||
sample_poisson_duration(&mut self.rng, self.config.average_message_sending_delay);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let next_delay = Box::pin(time::sleep(sampled));
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let next_delay = Box::pin(wasm_timer::Delay::new(sampled));
|
||||
|
||||
self.next_delay = Some(next_delay);
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
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))));
|
||||
}
|
||||
|
||||
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(),
|
||||
))))
|
||||
}
|
||||
|
||||
// if there's nothing, then there's nothing
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next_message(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<StreamMessage>> {
|
||||
if self.config.disable_poisson_packet_distribution {
|
||||
self.poll_immediate(cx)
|
||||
} else {
|
||||
self.poll_poisson(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[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 shutdown = self.shutdown.clone();
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
@@ -293,8 +527,23 @@ where
|
||||
log::debug!("OutQueueControl: Exiting");
|
||||
}
|
||||
|
||||
pub(crate) async fn run_out_queue_control(&mut self) {
|
||||
debug!("Starting out queue controller...");
|
||||
self.run_normal_out_queue().await
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started OutQueueControl without graceful shutdown support");
|
||||
|
||||
while let Some(next_message) = self.next().await {
|
||||
self.on_message(next_message).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Stream for OutQueueControl<R>
|
||||
where
|
||||
R: CryptoRng + Rng + Unpin,
|
||||
{
|
||||
type Item = StreamMessage;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.poll_next_message(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
use crate::client::SHUTDOWN_HAS_BEEN_SIGNALLED;
|
||||
use crate::spawn_future;
|
||||
use crypto::asymmetric::encryption;
|
||||
use crypto::symmetric::stream_cipher;
|
||||
use crypto::Digest;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
use gateway_client::MixnetMessageReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey};
|
||||
use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm};
|
||||
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use task::ShutdownListener;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use crypto::{symmetric::stream_cipher, Digest};
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey};
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm};
|
||||
|
||||
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
|
||||
// or to say "hey, I'm going offline, don't send anything more to me. Just buffer them instead"
|
||||
@@ -116,13 +117,14 @@ struct ReceivedMessagesBuffer {
|
||||
|
||||
/// Storage containing keys to all [`ReplySURB`]s ever sent out that we did not receive back.
|
||||
// There's no need to put it behind a Mutex since it's already properly concurrent
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBuffer {
|
||||
fn new(
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
) -> Self {
|
||||
ReceivedMessagesBuffer {
|
||||
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
||||
@@ -132,6 +134,7 @@ impl ReceivedMessagesBuffer {
|
||||
message_sender: None,
|
||||
recently_reconstructed: HashSet::new(),
|
||||
})),
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
}
|
||||
}
|
||||
@@ -180,6 +183,7 @@ impl ReceivedMessagesBuffer {
|
||||
self.inner.lock().await.messages.extend(msgs)
|
||||
}
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
fn process_received_reply(
|
||||
reply_ciphertext: &[u8],
|
||||
reply_key: SurbEncryptionKey,
|
||||
@@ -212,38 +216,50 @@ impl ReceivedMessagesBuffer {
|
||||
let mut completed_messages = Vec::new();
|
||||
let mut inner_guard = self.inner.lock().await;
|
||||
|
||||
let reply_surb_digest_size = ReplySurbKeyDigestAlgorithm::output_size();
|
||||
|
||||
// first check if this is a reply or a chunked message
|
||||
// TODO: verify with @AP if this way of doing it is safe or whether it could
|
||||
// cause some attacks due to, I don't know, stupid edge case collisions?
|
||||
// Update: this DOES introduce a possible leakage: https://github.com/nymtech/nym/issues/296
|
||||
for msg in msgs {
|
||||
let possible_key_digest =
|
||||
EncryptionKeyDigest::clone_from_slice(&msg[..reply_surb_digest_size]);
|
||||
// TODO:
|
||||
// 1. make it nicer
|
||||
// 2. make it not feature-locked
|
||||
|
||||
// check first `HasherOutputSize` bytes if they correspond to known encryption key
|
||||
// if yes - this is a reply message
|
||||
|
||||
// TODO: this might be a bottleneck - since the keys are stored on disk we, presumably,
|
||||
// are doing a disk operation every single received fragment
|
||||
if let Some(reply_encryption_key) = self
|
||||
.reply_key_storage
|
||||
.get_and_remove_encryption_key(possible_key_digest)
|
||||
.expect("storage operation failed!")
|
||||
#[cfg(feature = "reply-surb")]
|
||||
{
|
||||
if let Some(completed_message) = Self::process_received_reply(
|
||||
&msg[reply_surb_digest_size..],
|
||||
reply_encryption_key,
|
||||
) {
|
||||
completed_messages.push(completed_message)
|
||||
}
|
||||
} else {
|
||||
// otherwise - it's a 'normal' message
|
||||
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
|
||||
completed_messages.push(completed_message)
|
||||
let reply_surb_digest_size = ReplySurbKeyDigestAlgorithm::output_size();
|
||||
|
||||
let possible_key_digest =
|
||||
EncryptionKeyDigest::clone_from_slice(&msg[..reply_surb_digest_size]);
|
||||
|
||||
// check first `HasherOutputSize` bytes if they correspond to known encryption key
|
||||
// if yes - this is a reply message
|
||||
|
||||
// TODO: this might be a bottleneck - since the keys are stored on disk we, presumably,
|
||||
// are doing a disk operation every single received fragment
|
||||
if let Some(reply_encryption_key) = self
|
||||
.reply_key_storage
|
||||
.get_and_remove_encryption_key(possible_key_digest)
|
||||
.expect("storage operation failed!")
|
||||
{
|
||||
if let Some(completed_message) = Self::process_received_reply(
|
||||
&msg[reply_surb_digest_size..],
|
||||
reply_encryption_key,
|
||||
) {
|
||||
completed_messages.push(completed_message)
|
||||
}
|
||||
} else {
|
||||
// otherwise - it's a 'normal' message
|
||||
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
|
||||
completed_messages.push(completed_message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "reply-surb"))]
|
||||
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
|
||||
completed_messages.push(completed_message)
|
||||
}
|
||||
}
|
||||
|
||||
if !completed_messages.is_empty() {
|
||||
@@ -293,72 +309,97 @@ impl RequestReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
request = self.query_receiver.next() => {
|
||||
match request {
|
||||
Some(ReceivedBufferMessage::ReceiverAnnounce(sender)) => {
|
||||
self.received_buffer.connect_sender(sender).await;
|
||||
}
|
||||
Some(ReceivedBufferMessage::ReceiverDisconnect) => {
|
||||
self.received_buffer.disconnect_sender().await
|
||||
}
|
||||
None => {
|
||||
log::trace!("RequestReceiver: Stopping since channel closed");
|
||||
break;
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
async fn handle_message(&mut self, message: ReceivedBufferMessage) {
|
||||
match message {
|
||||
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
|
||||
self.received_buffer.connect_sender(sender).await;
|
||||
}
|
||||
ReceivedBufferMessage::ReceiverDisconnect => {
|
||||
self.received_buffer.disconnect_sender().await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(SHUTDOWN_HAS_BEEN_SIGNALLED.load(Ordering::Relaxed));
|
||||
log::debug!("RequestReceiver: Exiting");
|
||||
})
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
debug!("Started RequestReceiver with graceful shutdown support");
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("RequestReceiver: Received shutdown");
|
||||
}
|
||||
request = self.query_receiver.next() => {
|
||||
match request {
|
||||
Some(message) => self.handle_message(message).await,
|
||||
None => {
|
||||
log::trace!("RequestReceiver: Stopping since channel closed");
|
||||
break;
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("RequestReceiver: Exiting");
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn run(&mut self) {
|
||||
debug!("Started RequestReceiver without graceful shutdown support");
|
||||
|
||||
while let Some(message) = self.query_receiver.next().await {
|
||||
self.handle_message(message).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FragmentedMessageReceiver {
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
impl FragmentedMessageReceiver {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
shutdown: ShutdownListener,
|
||||
) -> Self {
|
||||
FragmentedMessageReceiver {
|
||||
received_buffer,
|
||||
mixnet_packet_receiver,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
fn start(mut self) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
new_messages = self.mixnet_packet_receiver.next() => match new_messages {
|
||||
Some(new_messages) => {
|
||||
self.received_buffer.handle_new_received(new_messages).await;
|
||||
}
|
||||
None => {
|
||||
log::trace!("FragmentedMessageReceiver: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.shutdown.recv() => {
|
||||
log::trace!("FragmentedMessageReceiver: Received shutdown");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
new_messages = self.mixnet_packet_receiver.next() => match new_messages {
|
||||
Some(new_messages) => {
|
||||
self.received_buffer.handle_new_received(new_messages).await;
|
||||
}
|
||||
None => {
|
||||
log::trace!("FragmentedMessageReceiver: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("FragmentedMessageReceiver: Received shutdown");
|
||||
}
|
||||
}
|
||||
assert!(self.shutdown.is_shutdown_poll());
|
||||
log::debug!("FragmentedMessageReceiver: Exiting");
|
||||
})
|
||||
}
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("FragmentedMessageReceiver: Exiting");
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn run(&mut self) {
|
||||
debug!("Started FragmentedMessageReceiver without graceful shutdown support");
|
||||
|
||||
while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
|
||||
self.received_buffer.handle_new_received(new_messages).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,25 +413,48 @@ impl ReceivedMessagesBufferController {
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
shutdown: ShutdownListener,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
) -> Self {
|
||||
let received_buffer =
|
||||
ReceivedMessagesBuffer::new(local_encryption_keypair, reply_key_storage);
|
||||
let received_buffer = ReceivedMessagesBuffer::new(
|
||||
local_encryption_keypair,
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
);
|
||||
|
||||
ReceivedMessagesBufferController {
|
||||
fragmented_message_receiver: FragmentedMessageReceiver::new(
|
||||
received_buffer.clone(),
|
||||
mixnet_packet_receiver,
|
||||
shutdown,
|
||||
),
|
||||
request_receiver: RequestReceiver::new(received_buffer, query_receiver),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
|
||||
let mut fragmented_message_receiver = self.fragmented_message_receiver;
|
||||
let mut request_receiver = self.request_receiver;
|
||||
|
||||
let shutdown_handle = shutdown.clone();
|
||||
spawn_future(async move {
|
||||
fragmented_message_receiver
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
});
|
||||
spawn_future(async move {
|
||||
request_receiver.run_with_shutdown(shutdown).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn start(self) {
|
||||
// TODO: should we do anything with JoinHandle(s) returned by start methods?
|
||||
self.fragmented_message_receiver.start();
|
||||
self.request_receiver.start();
|
||||
let mut fragmented_message_receiver = self.fragmented_message_receiver;
|
||||
let mut request_receiver = self.request_receiver;
|
||||
spawn_future(async move {
|
||||
fragmented_message_receiver.run().await;
|
||||
});
|
||||
spawn_future(async move {
|
||||
request_receiver.run().await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::spawn_future;
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::params::DEFAULT_NUM_MIX_HOPS;
|
||||
@@ -10,9 +11,7 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use std::time::Duration;
|
||||
use task::ShutdownListener;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use tokio::task::JoinHandle;
|
||||
use topology::{nym_topology_from_detailed, NymTopology};
|
||||
use url::Url;
|
||||
|
||||
@@ -58,24 +57,15 @@ impl<'a> TopologyReadPermit<'a> {
|
||||
) -> Option<&'a NymTopology> {
|
||||
// Note: implicit deref with Deref for TopologyReadPermit is happening here
|
||||
let topology_ref_option = self.permit.as_ref();
|
||||
match topology_ref_option {
|
||||
None => None,
|
||||
Some(topology_ref) => {
|
||||
// see if it's possible to route the packet to both gateways
|
||||
if !topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|
||||
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|
||||
|| if let Some(packet_recipient) = packet_recipient {
|
||||
!topology_ref.gateway_exists(packet_recipient.gateway())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
{
|
||||
None
|
||||
topology_ref_option.as_ref().filter(|topology_ref| {
|
||||
!(!topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|
||||
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|
||||
|| if let Some(packet_recipient) = packet_recipient {
|
||||
!topology_ref.gateway_exists(packet_recipient.gateway())
|
||||
} else {
|
||||
Some(topology_ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,8 +294,11 @@ impl TopologyRefresher {
|
||||
self.topology_accessor.is_routable().await
|
||||
}
|
||||
|
||||
pub fn start(mut self, mut shutdown: ShutdownListener) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
|
||||
spawn_future(async move {
|
||||
debug!("Started TopologyRefresher with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(self.refresh_rate) => {
|
||||
@@ -320,4 +313,17 @@ impl TopologyRefresher {
|
||||
log::debug!("TopologyRefresher: Exiting");
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn start(mut self) {
|
||||
use futures::StreamExt;
|
||||
|
||||
spawn_future(async move {
|
||||
let mut interval =
|
||||
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
|
||||
while let Some(_) = interval.next().await {
|
||||
self.refresh().await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use config::NymConfig;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub mod persistence;
|
||||
|
||||
pub const MISSING_VALUE: &str = "MISSING VALUE";
|
||||
@@ -236,6 +240,18 @@ impl<T: NymConfig> Config<T> {
|
||||
self.debug.topology_resolution_timeout
|
||||
}
|
||||
|
||||
pub fn get_disabled_loop_cover_traffic_stream(&self) -> bool {
|
||||
self.debug.disable_loop_cover_traffic_stream
|
||||
}
|
||||
|
||||
pub fn get_disabled_main_poisson_packet_distribution(&self) -> bool {
|
||||
self.debug.disable_main_poisson_packet_distribution
|
||||
}
|
||||
|
||||
pub fn get_use_extended_packet_size(&self) -> Option<ExtendedPacketSize> {
|
||||
self.debug.use_extended_packet_size.clone()
|
||||
}
|
||||
|
||||
pub fn get_version(&self) -> &str {
|
||||
&self.client.version
|
||||
}
|
||||
@@ -252,6 +268,7 @@ impl<T: NymConfig> Default for Config<T> {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))]
|
||||
pub struct GatewayEndpoint {
|
||||
/// gateway_id specifies ID of the gateway to which the client should send messages.
|
||||
/// If initially omitted, a random gateway will be chosen from the available topology.
|
||||
@@ -398,53 +415,72 @@ pub struct Debug {
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
#[serde(with = "humantime_serde")]
|
||||
average_packet_delay: Duration,
|
||||
pub average_packet_delay: Duration,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent acknowledgement is going to be delayed at any given mix node.
|
||||
/// So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
#[serde(with = "humantime_serde")]
|
||||
average_ack_delay: Duration,
|
||||
pub average_ack_delay: Duration,
|
||||
|
||||
/// Value multiplied with the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 1.
|
||||
ack_wait_multiplier: f64,
|
||||
pub ack_wait_multiplier: f64,
|
||||
|
||||
/// Value added to the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 0.
|
||||
#[serde(with = "humantime_serde")]
|
||||
ack_wait_addition: Duration,
|
||||
pub ack_wait_addition: Duration,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
loop_cover_traffic_average_delay: Duration,
|
||||
pub loop_cover_traffic_average_delay: Duration,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
#[serde(with = "humantime_serde")]
|
||||
message_sending_average_delay: Duration,
|
||||
pub message_sending_average_delay: Duration,
|
||||
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
#[serde(with = "humantime_serde")]
|
||||
gateway_response_timeout: Duration,
|
||||
pub gateway_response_timeout: Duration,
|
||||
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
#[serde(with = "humantime_serde")]
|
||||
topology_refresh_rate: Duration,
|
||||
pub topology_refresh_rate: Duration,
|
||||
|
||||
/// During topology refresh, test packets are sent through every single possible network
|
||||
/// path. This timeout determines waiting period until it is decided that the packet
|
||||
/// did not reach its destination.
|
||||
#[serde(with = "humantime_serde")]
|
||||
topology_resolution_timeout: Duration,
|
||||
pub topology_resolution_timeout: Duration,
|
||||
|
||||
/// Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
|
||||
pub disable_loop_cover_traffic_stream: bool,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Controls whether the sent sphinx packet use a NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: Option<ExtendedPacketSize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ExtendedPacketSize {
|
||||
Extended8,
|
||||
Extended16,
|
||||
Extended32,
|
||||
}
|
||||
|
||||
impl Default for Debug {
|
||||
@@ -459,6 +495,19 @@ impl Default for Debug {
|
||||
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
disable_loop_cover_traffic_stream: false,
|
||||
disable_main_poisson_packet_distribution: false,
|
||||
use_extended_packet_size: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExtendedPacketSize> for PacketSize {
|
||||
fn from(size: ExtendedPacketSize) -> PacketSize {
|
||||
match size {
|
||||
ExtendedPacketSize::Extended8 => PacketSize::ExtendedPacket8,
|
||||
ExtendedPacketSize::Extended16 => PacketSize::ExtendedPacket16,
|
||||
ExtendedPacketSize::Extended32 => PacketSize::ExtendedPacket32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,6 @@ pub enum ClientCoreError {
|
||||
ListOfValidatorApisIsEmpty,
|
||||
#[error("Could not load existing gateway configuration: {0}")]
|
||||
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
|
||||
#[error("The current network topology seem to be insufficient to route any packets through")]
|
||||
InsufficientNetworkTopology,
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ async fn register_with_gateway(
|
||||
gateway.owner.clone(),
|
||||
our_identity.clone(),
|
||||
timeout,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
None,
|
||||
);
|
||||
gateway_client
|
||||
|
||||
@@ -1,4 +1,23 @@
|
||||
use std::future::Future;
|
||||
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod init;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(crate) fn spawn_future<F>(future: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(future);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn spawn_future<F>(future: F)
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
tokio::spawn(future);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ rand = "0.7.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
|
||||
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
config = { path = "../../common/config" }
|
||||
|
||||
@@ -27,7 +27,8 @@ pretty_env_logger = "0.4" # for formatting log messages
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
|
||||
serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
|
||||
sled = "0.34" # for storage of replySURB decryption keys
|
||||
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal"] } # async runtime
|
||||
thiserror = "1.0.34"
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime
|
||||
tokio-tungstenite = "0.14" # websocket
|
||||
|
||||
## internal
|
||||
@@ -38,6 +39,7 @@ completions = { path = "../../common/completions" }
|
||||
credential-storage = { path = "../../common/credential-storage" }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
logging = { path = "../../common/logging"}
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
@@ -6,9 +6,7 @@ use client_core::client::inbound_messages::{
|
||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||
};
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::client::mix_traffic::{
|
||||
BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController,
|
||||
};
|
||||
use client_core::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
|
||||
use client_core::client::real_messages_control;
|
||||
use client_core::client::real_messages_control::RealMessagesController;
|
||||
use client_core::client::received_buffer::{
|
||||
@@ -20,6 +18,7 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use client_core::error::ClientCoreError;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
@@ -35,6 +34,7 @@ use nymsphinx::receiver::ReconstructedMessage;
|
||||
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
|
||||
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use crate::error::ClientError;
|
||||
use crate::websocket;
|
||||
|
||||
pub(crate) mod config;
|
||||
@@ -90,7 +90,7 @@ impl NymClient {
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
|
||||
LoopCoverTrafficStream::new(
|
||||
let mut stream = LoopCoverTrafficStream::new(
|
||||
self.key_manager.ack_key(),
|
||||
self.config.get_base().get_average_ack_delay(),
|
||||
self.config.get_base().get_average_packet_delay(),
|
||||
@@ -100,9 +100,14 @@ impl NymClient {
|
||||
mix_tx,
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
shutdown,
|
||||
)
|
||||
.start();
|
||||
);
|
||||
|
||||
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
stream.set_custom_packet_size(size.into());
|
||||
}
|
||||
|
||||
stream.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
fn start_real_traffic_controller(
|
||||
@@ -114,16 +119,24 @@ impl NymClient {
|
||||
mix_sender: BatchMixMessageSender,
|
||||
shutdown: ShutdownListener,
|
||||
) {
|
||||
let controller_config = real_messages_control::Config::new(
|
||||
let mut controller_config = real_messages_control::Config::new(
|
||||
self.key_manager.ack_key(),
|
||||
self.config.get_base().get_ack_wait_multiplier(),
|
||||
self.config.get_base().get_ack_wait_addition(),
|
||||
self.config.get_base().get_average_ack_delay(),
|
||||
self.config.get_base().get_message_sending_average_delay(),
|
||||
self.config.get_base().get_average_packet_delay(),
|
||||
self.config
|
||||
.get_base()
|
||||
.get_disabled_main_poisson_packet_distribution(),
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
controller_config.set_custom_packet_size(size.into());
|
||||
}
|
||||
|
||||
info!("Starting real traffic stream...");
|
||||
|
||||
RealMessagesController::new(
|
||||
@@ -133,9 +146,8 @@ impl NymClient {
|
||||
mix_sender,
|
||||
topology_accessor,
|
||||
reply_key_storage,
|
||||
shutdown,
|
||||
)
|
||||
.start();
|
||||
.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -153,9 +165,8 @@ impl NymClient {
|
||||
query_receiver,
|
||||
mixnet_receiver,
|
||||
reply_key_storage,
|
||||
shutdown,
|
||||
)
|
||||
.start()
|
||||
.start_with_shutdown(shutdown)
|
||||
}
|
||||
|
||||
async fn start_gateway_client(
|
||||
@@ -223,7 +234,7 @@ impl NymClient {
|
||||
&mut self,
|
||||
topology_accessor: TopologyAccessor,
|
||||
shutdown: ShutdownListener,
|
||||
) {
|
||||
) -> Result<(), ClientError> {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
self.config.get_base().get_topology_refresh_rate(),
|
||||
@@ -238,14 +249,16 @@ impl NymClient {
|
||||
|
||||
// TODO: a slightly more graceful termination here
|
||||
if !topology_refresher.is_topology_routable().await {
|
||||
panic!(
|
||||
"The current network topology seem to be insufficient to route any packets through\
|
||||
log::error!(
|
||||
"The current network topology seem to be insufficient to route any packets through \
|
||||
- check if enough nodes and a gateway are online"
|
||||
);
|
||||
return Err(ClientCoreError::InsufficientNetworkTopology.into());
|
||||
}
|
||||
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start(shutdown);
|
||||
topology_refresher.start_with_shutdown(shutdown);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||
@@ -253,13 +266,13 @@ impl NymClient {
|
||||
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
|
||||
// requests?
|
||||
fn start_mix_traffic_controller(
|
||||
&mut self,
|
||||
mix_rx: BatchMixMessageReceiver,
|
||||
gateway_client: GatewayClient,
|
||||
shutdown: ShutdownListener,
|
||||
) {
|
||||
) -> BatchMixMessageSender {
|
||||
info!("Starting mix traffic controller...");
|
||||
MixTrafficController::new(mix_rx, gateway_client, shutdown).start();
|
||||
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
|
||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
||||
mix_tx
|
||||
}
|
||||
|
||||
fn start_websocket_listener(
|
||||
@@ -319,8 +332,8 @@ impl NymClient {
|
||||
}
|
||||
|
||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||
pub async fn run_forever(&mut self) {
|
||||
let shutdown = self.start().await;
|
||||
pub async fn run_forever(&mut self) -> Result<(), ClientError> {
|
||||
let shutdown = self.start().await?;
|
||||
wait_for_signal().await;
|
||||
|
||||
println!(
|
||||
@@ -337,20 +350,16 @@ impl NymClient {
|
||||
//shutdown.wait_for_shutdown().await;
|
||||
|
||||
log::info!("Stopping nym-client");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) -> ShutdownNotifier {
|
||||
pub async fn start(&mut self) -> Result<ShutdownNotifier, ClientError> {
|
||||
info!("Starting nym client");
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
// rather than creating them here, so say for example the buffer controller would create the request channels
|
||||
// and would allow anyone to clone the sender channel
|
||||
|
||||
// sphinx_message_sender is the transmitter for any component generating sphinx packets that are to be sent to the mixnet
|
||||
// they are used by cover traffic stream and real traffic stream
|
||||
// sphinx_message_receiver is the receiver used by MixTrafficController that sends the actual traffic
|
||||
let (sphinx_message_sender, sphinx_message_receiver) = mpsc::unbounded();
|
||||
|
||||
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
|
||||
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
|
||||
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
|
||||
@@ -375,7 +384,7 @@ impl NymClient {
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
|
||||
.await;
|
||||
.await?;
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
@@ -387,11 +396,13 @@ impl NymClient {
|
||||
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
|
||||
.await;
|
||||
|
||||
self.start_mix_traffic_controller(
|
||||
sphinx_message_receiver,
|
||||
gateway_client,
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
|
||||
// that are to be sent to the mixnet. They are used by cover traffic stream and real
|
||||
// traffic stream.
|
||||
// The MixTrafficController then sends the actual traffic
|
||||
let sphinx_message_sender =
|
||||
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
|
||||
|
||||
self.start_real_traffic_controller(
|
||||
shared_topology_accessor.clone(),
|
||||
reply_key_storage,
|
||||
@@ -401,11 +412,17 @@ impl NymClient {
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
|
||||
self.start_cover_traffic_stream(
|
||||
shared_topology_accessor,
|
||||
sphinx_message_sender,
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
if !self
|
||||
.config
|
||||
.get_base()
|
||||
.get_disabled_loop_cover_traffic_stream()
|
||||
{
|
||||
self.start_cover_traffic_stream(
|
||||
shared_topology_accessor,
|
||||
sphinx_message_sender,
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
}
|
||||
|
||||
match self.config.get_socket_type() {
|
||||
SocketType::WebSocket => {
|
||||
@@ -431,6 +448,6 @@ impl NymClient {
|
||||
info!("Client startup finished!");
|
||||
info!("The address of this client is: {}", self.as_mix_recipient());
|
||||
|
||||
shutdown
|
||||
Ok(shutdown)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use crate::error::ClientError;
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use completions::{fig_generate, ArgShell};
|
||||
@@ -83,16 +84,17 @@ pub(crate) struct OverrideConfig {
|
||||
enabled_credentials_mode: bool,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Cli) {
|
||||
pub(crate) async fn execute(args: &Cli) -> Result<(), ClientError> {
|
||||
let bin_name = "nym-native-client";
|
||||
|
||||
match &args.command {
|
||||
Commands::Init(m) => init::execute(m).await,
|
||||
Commands::Run(m) => run::execute(m).await,
|
||||
Commands::Run(m) => run::execute(m).await?,
|
||||
Commands::Upgrade(m) => upgrade::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::{
|
||||
client::{config::Config, NymClient},
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::ClientError,
|
||||
};
|
||||
|
||||
use clap::Args;
|
||||
@@ -73,14 +74,14 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) {
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> {
|
||||
let id = &args.id;
|
||||
|
||||
let mut config = match Config::load_from_file(Some(id)) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
|
||||
return;
|
||||
return Err(ClientError::FailedToLoadConfig(id.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -89,8 +90,8 @@ pub(crate) async fn execute(args: &Run) {
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return;
|
||||
return Err(ClientError::FailedLocalVersionCheck);
|
||||
}
|
||||
|
||||
NymClient::new(config).run_forever().await;
|
||||
NymClient::new(config).run_forever().await
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
use client_core::error::ClientCoreError;
|
||||
use crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use gateway_client::error::GatewayClientError;
|
||||
use validator_client::ValidatorClientError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientError {
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Gateway client error: {0}")]
|
||||
GatewayClientError(#[from] GatewayClientError),
|
||||
#[error("Ed25519 error: {0}")]
|
||||
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
|
||||
#[error("Validator client error: {0}")]
|
||||
ValidatorClientError(#[from] ValidatorClientError),
|
||||
#[error("client-core error: {0}")]
|
||||
ClientCoreError(#[from] ClientCoreError),
|
||||
|
||||
#[error("Failed to load config for: {0}")]
|
||||
FailedToLoadConfig(String),
|
||||
#[error("Failed local version check, client and config mismatch")]
|
||||
FailedLocalVersionCheck,
|
||||
}
|
||||
@@ -2,4 +2,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod websocket;
|
||||
|
||||
@@ -2,20 +2,23 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, Parser};
|
||||
use error::ClientError;
|
||||
use logging::setup_logging;
|
||||
use network_defaults::setup_env;
|
||||
|
||||
pub mod client;
|
||||
pub mod commands;
|
||||
pub mod error;
|
||||
pub mod websocket;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> Result<(), ClientError> {
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let args = commands::Cli::parse();
|
||||
setup_env(args.config_env_file.clone());
|
||||
commands::execute(&args).await;
|
||||
commands::execute(&args).await
|
||||
}
|
||||
|
||||
fn banner() -> String {
|
||||
@@ -34,25 +37,3 @@ fn banner() -> String {
|
||||
crate_version!()
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log_builder.parse_filters(&s);
|
||||
} else {
|
||||
// default to 'Info'
|
||||
log_builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
log_builder
|
||||
.filter_module("hyper", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_reactor", log::LevelFilter::Warn)
|
||||
.filter_module("reqwest", log::LevelFilter::Warn)
|
||||
.filter_module("mio", log::LevelFilter::Warn)
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("handlebars", log::LevelFilter::Warn)
|
||||
.filter_module("sled", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ pretty_env_logger = "0.4"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization
|
||||
snafu = "0.6"
|
||||
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal"] }
|
||||
thiserror = "1.0.34"
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] }
|
||||
url = "2.2"
|
||||
|
||||
# internal
|
||||
@@ -31,6 +32,7 @@ completions = { path = "../../common/completions" }
|
||||
credential-storage = { path = "../../common/credential-storage" }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
logging = { path = "../../common/logging"}
|
||||
gateway-client = { path = "../../common/client-libs/gateway-client" }
|
||||
gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
@@ -3,14 +3,18 @@
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::error::Socks5ClientError;
|
||||
use crate::socks::{
|
||||
authentication::{AuthenticationMethods, Authenticator, User},
|
||||
server::SphinxSocksServer,
|
||||
};
|
||||
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use client_core::client::inbound_messages::{
|
||||
InputMessage, InputMessageReceiver, InputMessageSender,
|
||||
};
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::client::mix_traffic::{
|
||||
BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController,
|
||||
};
|
||||
use client_core::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
|
||||
use client_core::client::real_messages_control::RealMessagesController;
|
||||
use client_core::client::received_buffer::{
|
||||
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
|
||||
@@ -20,6 +24,7 @@ use client_core::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use client_core::error::ClientCoreError;
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
@@ -33,12 +38,6 @@ use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::socks::{
|
||||
authentication::{AuthenticationMethods, Authenticator, User},
|
||||
server::SphinxSocksServer,
|
||||
};
|
||||
|
||||
pub mod config;
|
||||
|
||||
// Channels used to control the main task from outside
|
||||
@@ -91,7 +90,7 @@ impl NymClient {
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
|
||||
LoopCoverTrafficStream::new(
|
||||
let mut stream = LoopCoverTrafficStream::new(
|
||||
self.key_manager.ack_key(),
|
||||
self.config.get_base().get_average_ack_delay(),
|
||||
self.config.get_base().get_average_packet_delay(),
|
||||
@@ -101,9 +100,14 @@ impl NymClient {
|
||||
mix_tx,
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
shutdown,
|
||||
)
|
||||
.start();
|
||||
);
|
||||
|
||||
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
stream.set_custom_packet_size(size.into());
|
||||
}
|
||||
|
||||
stream.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
fn start_real_traffic_controller(
|
||||
@@ -115,16 +119,24 @@ impl NymClient {
|
||||
mix_sender: BatchMixMessageSender,
|
||||
shutdown: ShutdownListener,
|
||||
) {
|
||||
let controller_config = client_core::client::real_messages_control::Config::new(
|
||||
let mut controller_config = client_core::client::real_messages_control::Config::new(
|
||||
self.key_manager.ack_key(),
|
||||
self.config.get_base().get_ack_wait_multiplier(),
|
||||
self.config.get_base().get_ack_wait_addition(),
|
||||
self.config.get_base().get_average_ack_delay(),
|
||||
self.config.get_base().get_message_sending_average_delay(),
|
||||
self.config.get_base().get_average_packet_delay(),
|
||||
self.config
|
||||
.get_base()
|
||||
.get_disabled_main_poisson_packet_distribution(),
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
controller_config.set_custom_packet_size(size.into());
|
||||
}
|
||||
|
||||
info!("Starting real traffic stream...");
|
||||
|
||||
RealMessagesController::new(
|
||||
@@ -134,9 +146,8 @@ impl NymClient {
|
||||
mix_sender,
|
||||
topology_accessor,
|
||||
reply_key_storage,
|
||||
shutdown,
|
||||
)
|
||||
.start();
|
||||
.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -154,9 +165,8 @@ impl NymClient {
|
||||
query_receiver,
|
||||
mixnet_receiver,
|
||||
reply_key_storage,
|
||||
shutdown,
|
||||
)
|
||||
.start()
|
||||
.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
async fn start_gateway_client(
|
||||
@@ -224,7 +234,7 @@ impl NymClient {
|
||||
&mut self,
|
||||
topology_accessor: TopologyAccessor,
|
||||
shutdown: ShutdownListener,
|
||||
) {
|
||||
) -> Result<(), Socks5ClientError> {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
self.config.get_base().get_validator_api_endpoints(),
|
||||
self.config.get_base().get_topology_refresh_rate(),
|
||||
@@ -239,14 +249,16 @@ impl NymClient {
|
||||
|
||||
// TODO: a slightly more graceful termination here
|
||||
if !topology_refresher.is_topology_routable().await {
|
||||
panic!(
|
||||
"The current network topology seem to be insufficient to route any packets through\
|
||||
log::error!(
|
||||
"The current network topology seem to be insufficient to route any packets through \
|
||||
- check if enough nodes and a gateway are online"
|
||||
);
|
||||
return Err(ClientCoreError::InsufficientNetworkTopology.into());
|
||||
}
|
||||
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start(shutdown);
|
||||
topology_refresher.start_with_shutdown(shutdown);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||
@@ -254,13 +266,13 @@ impl NymClient {
|
||||
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
|
||||
// requests?
|
||||
fn start_mix_traffic_controller(
|
||||
&mut self,
|
||||
mix_rx: BatchMixMessageReceiver,
|
||||
gateway_client: GatewayClient,
|
||||
shutdown: ShutdownListener,
|
||||
) {
|
||||
) -> BatchMixMessageSender {
|
||||
info!("Starting mix traffic controller...");
|
||||
MixTrafficController::new(mix_rx, gateway_client, shutdown).start();
|
||||
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
|
||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
||||
mix_tx
|
||||
}
|
||||
|
||||
fn start_socks5_listener(
|
||||
@@ -285,8 +297,8 @@ impl NymClient {
|
||||
}
|
||||
|
||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||
pub async fn run_forever(&mut self) {
|
||||
let mut shutdown = self.start().await;
|
||||
pub async fn run_forever(&mut self) -> Result<(), Socks5ClientError> {
|
||||
let mut shutdown = self.start().await?;
|
||||
wait_for_signal().await;
|
||||
|
||||
log::info!("Sending shutdown");
|
||||
@@ -297,11 +309,15 @@ impl NymClient {
|
||||
shutdown.wait_for_shutdown().await;
|
||||
|
||||
log::info!("Stopping nym-socks5-client");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Variant of `run_forever` that listends for remote control messages
|
||||
pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) {
|
||||
let mut shutdown = self.start().await;
|
||||
pub async fn run_and_listen(
|
||||
&mut self,
|
||||
mut receiver: Socks5ControlMessageReceiver,
|
||||
) -> Result<(), Socks5ClientError> {
|
||||
let mut shutdown = self.start().await?;
|
||||
tokio::select! {
|
||||
message = receiver.next() => {
|
||||
log::debug!("Received message: {:?}", message);
|
||||
@@ -327,20 +343,16 @@ impl NymClient {
|
||||
shutdown.wait_for_shutdown().await;
|
||||
|
||||
log::info!("Stopping nym-socks5-client");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) -> ShutdownNotifier {
|
||||
pub async fn start(&mut self) -> Result<ShutdownNotifier, Socks5ClientError> {
|
||||
info!("Starting nym client");
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
// rather than creating them here, so say for example the buffer controller would create the request channels
|
||||
// and would allow anyone to clone the sender channel
|
||||
|
||||
// sphinx_message_sender is the transmitter for any component generating sphinx packets that are to be sent to the mixnet
|
||||
// they are used by cover traffic stream and real traffic stream
|
||||
// sphinx_message_receiver is the receiver used by MixTrafficController that sends the actual traffic
|
||||
let (sphinx_message_sender, sphinx_message_receiver) = mpsc::unbounded();
|
||||
|
||||
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
|
||||
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
|
||||
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
|
||||
@@ -365,7 +377,7 @@ impl NymClient {
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
|
||||
.await;
|
||||
.await?;
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
@@ -377,11 +389,13 @@ impl NymClient {
|
||||
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
|
||||
.await;
|
||||
|
||||
self.start_mix_traffic_controller(
|
||||
sphinx_message_receiver,
|
||||
gateway_client,
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
|
||||
// that are to be sent to the mixnet. They are used by cover traffic stream and real
|
||||
// traffic stream.
|
||||
// The MixTrafficController then sends the actual traffic
|
||||
let sphinx_message_sender =
|
||||
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
|
||||
|
||||
self.start_real_traffic_controller(
|
||||
shared_topology_accessor.clone(),
|
||||
reply_key_storage,
|
||||
@@ -391,11 +405,18 @@ impl NymClient {
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
|
||||
self.start_cover_traffic_stream(
|
||||
shared_topology_accessor,
|
||||
sphinx_message_sender,
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
if !self
|
||||
.config
|
||||
.get_base()
|
||||
.get_disabled_loop_cover_traffic_stream()
|
||||
{
|
||||
self.start_cover_traffic_stream(
|
||||
shared_topology_accessor,
|
||||
sphinx_message_sender,
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
}
|
||||
|
||||
self.start_socks5_listener(
|
||||
received_buffer_request_sender,
|
||||
input_sender,
|
||||
@@ -405,6 +426,6 @@ impl NymClient {
|
||||
info!("Client startup finished!");
|
||||
info!("The address of this client is: {}", self.as_mix_recipient());
|
||||
|
||||
shutdown
|
||||
Ok(shutdown)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::error::Socks5ClientError;
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use completions::{fig_generate, ArgShell};
|
||||
@@ -83,16 +84,17 @@ pub(crate) struct OverrideConfig {
|
||||
enabled_credentials_mode: bool,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Cli) {
|
||||
pub(crate) async fn execute(args: &Cli) -> Result<(), Socks5ClientError> {
|
||||
let bin_name = "nym-socks5-client";
|
||||
|
||||
match &args.command {
|
||||
Commands::Init(m) => init::execute(m).await,
|
||||
Commands::Run(m) => run::execute(m).await,
|
||||
Commands::Run(m) => run::execute(m).await?,
|
||||
Commands::Upgrade(m) => upgrade::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::{
|
||||
client::{config::Config, NymClient},
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::Socks5ClientError,
|
||||
};
|
||||
|
||||
use clap::Args;
|
||||
@@ -80,14 +81,14 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) {
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Socks5ClientError> {
|
||||
let id = &args.id;
|
||||
|
||||
let mut config = match Config::load_from_file(Some(id)) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
|
||||
return;
|
||||
return Err(Socks5ClientError::FailedToLoadConfig(id.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,8 +97,8 @@ pub(crate) async fn execute(args: &Run) {
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return;
|
||||
return Err(Socks5ClientError::FailedLocalVersionCheck);
|
||||
}
|
||||
|
||||
NymClient::new(config).run_forever().await;
|
||||
NymClient::new(config).run_forever().await
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
use client_core::error::ClientCoreError;
|
||||
use crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use gateway_client::error::GatewayClientError;
|
||||
use validator_client::ValidatorClientError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Socks5ClientError {
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Gateway client error: {0}")]
|
||||
GatewayClientError(#[from] GatewayClientError),
|
||||
#[error("Ed25519 error: {0}")]
|
||||
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
|
||||
#[error("Validator client error: {0}")]
|
||||
ValidatorClientError(#[from] ValidatorClientError),
|
||||
#[error("client-core error: {0}")]
|
||||
ClientCoreError(#[from] ClientCoreError),
|
||||
|
||||
#[error("Failed to load config for: {0}")]
|
||||
FailedToLoadConfig(String),
|
||||
#[error("Failed local version check, client and config mismatch")]
|
||||
FailedLocalVersionCheck,
|
||||
}
|
||||
@@ -2,4 +2,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod socks;
|
||||
|
||||
@@ -2,20 +2,23 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, Parser};
|
||||
use error::Socks5ClientError;
|
||||
use logging::setup_logging;
|
||||
use network_defaults::setup_env;
|
||||
|
||||
pub mod client;
|
||||
mod commands;
|
||||
pub mod error;
|
||||
pub mod socks;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> Result<(), Socks5ClientError> {
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let args = commands::Cli::parse();
|
||||
setup_env(args.config_env_file.clone());
|
||||
commands::execute(&args).await;
|
||||
commands::execute(&args).await
|
||||
}
|
||||
|
||||
fn banner() -> String {
|
||||
@@ -34,23 +37,3 @@ fn banner() -> String {
|
||||
crate_version!()
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log_builder.parse_filters(&s);
|
||||
} else {
|
||||
// default to 'Info'
|
||||
log_builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
log_builder
|
||||
.filter_module("hyper", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_reactor", log::LevelFilter::Warn)
|
||||
.filter_module("reqwest", log::LevelFilter::Warn)
|
||||
.filter_module("mio", log::LevelFilter::Warn)
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -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.0"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
|
||||
license = "Apache-2.0"
|
||||
@@ -20,13 +20,15 @@ coconut = ["coconut-interface", "credentials", "gateway-client/coconut"]
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
wasm-bindgen = { version = "=0.2.78", features = ["serde-serialize"] }
|
||||
serde-wasm-bindgen = "0.4"
|
||||
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
url = "2.2"
|
||||
|
||||
# internal
|
||||
client-core = { path = "../client-core", default-features = false, features = ["wasm"] }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
credentials = { path = "../../common/credentials", optional = true }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
@@ -53,6 +55,8 @@ wee_alloc = { version = "0.4", optional = true }
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
|
||||
wasm-opt = true
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 'z'
|
||||
@@ -4,6 +4,16 @@ This example application demonstrates how to use WebAssembly to create Sphinx pa
|
||||
|
||||
## 🚴 Usage
|
||||
|
||||
Build the WASM package for bundling:
|
||||
|
||||
```
|
||||
wasm-pack build --scope nymproject --target no-modules
|
||||
```
|
||||
|
||||
in the `clients/webassembly` directory (one up).
|
||||
|
||||
Start the webpack dev server:
|
||||
|
||||
```
|
||||
npm install # set up dependencies
|
||||
npm run start # starts a web server at http://localhost:8001
|
||||
@@ -15,4 +25,4 @@ Check your dev console for output.
|
||||
|
||||
Install `wasm-pack`. Instruction are at the [Rust WASM tutorial](https://rustwasm.github.io/docs/book/game-of-life/hello-world.html).
|
||||
|
||||
`wasm-pack build` in the `clients/webassembly` directory (one up) will rebuild the wasm package if you make changes to the Rust source. That will be automatically picked up (and reloaded, if need be) by the npm dev server.
|
||||
`wasm-pack build --scope nymproject --target no-modules` in the `clients/webassembly` directory (one up) will rebuild the wasm package if you make changes to the Rust source. That will be automatically picked up (and reloaded, if need be) by the npm dev server.
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
// A dependency graph that contains any wasm must all be imported
|
||||
// asynchronously. This `bootstrap.js` file does the single async import, so
|
||||
// that no one else needs to worry about it again.
|
||||
import("./index.js")
|
||||
.catch(e => console.error("Error importing `index.js`:", e));
|
||||
import('./index.js')
|
||||
.catch(e => console.error('Error importing `index.js`:', e));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 Nym Technologies SA
|
||||
// Copyright 2020-2022 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,55 +12,67 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {
|
||||
NymClient,
|
||||
set_panic_hook
|
||||
} from "@nymproject/nym-client-wasm"
|
||||
class WebWorkerClient {
|
||||
worker = null;
|
||||
|
||||
// current limitation of rust-wasm for async stuff : (
|
||||
let client = null
|
||||
constructor() {
|
||||
this.worker = new Worker('./worker.js');
|
||||
|
||||
this.worker.onmessage = (ev) => {
|
||||
if (ev.data && ev.data.kind) {
|
||||
switch (ev.data.kind) {
|
||||
case 'Ready':
|
||||
const { selfAddress } = ev.data.args;
|
||||
displaySenderAddress(selfAddress);
|
||||
break;
|
||||
case 'ReceiveMessage':
|
||||
const { message } = ev.data.args;
|
||||
displayReceived(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
sendMessage = (message, recipient) => {
|
||||
if (!this.worker) {
|
||||
console.error('Could not send message because worker does not exist');
|
||||
return;
|
||||
}
|
||||
|
||||
this.worker.postMessage({
|
||||
kind: 'SendMessage',
|
||||
args: {
|
||||
message, recipient,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
let client = null;
|
||||
|
||||
async function main() {
|
||||
// sets up better stack traces in case of in-rust panics
|
||||
set_panic_hook();
|
||||
client = new WebWorkerClient();
|
||||
|
||||
// validator server we will use to get topology from
|
||||
const validator = "https://validator.nymtech.net/api";
|
||||
|
||||
client = new NymClient(validator);
|
||||
|
||||
const on_message = (msg) => displayReceived(msg);
|
||||
const on_connect = () => console.log("Established (and authenticated) gateway connection!");
|
||||
|
||||
client.set_on_message(on_message);
|
||||
client.set_on_gateway_connect(on_connect);
|
||||
|
||||
// this is current limitation of wasm in rust - for async methods you can't take self my reference...
|
||||
// I'm trying to figure out if I can somehow hack my way around it, but for time being you have to re-assign
|
||||
// the object (it's the same one)
|
||||
client = await client.initial_setup();
|
||||
|
||||
const self_address = client.self_address();
|
||||
displaySenderAddress(self_address);
|
||||
|
||||
const sendButton = document.querySelector('#send-button');
|
||||
sendButton.onclick = function () {
|
||||
sendMessageTo();
|
||||
}
|
||||
const sendButton = document.querySelector('#send-button');
|
||||
sendButton.onclick = function() {
|
||||
sendMessageTo();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Sphinx packet and send it to the mixnet through the gateway node.
|
||||
*
|
||||
*
|
||||
* Message and recipient are taken from the values in the user interface.
|
||||
*
|
||||
* @param {Client} nymClient the nym client to use for message sending
|
||||
*/
|
||||
async function sendMessageTo() {
|
||||
const message = document.getElementById("message").value;
|
||||
const recipient = document.getElementById("recipient").value;
|
||||
client = await client.send_message(message, recipient);
|
||||
displaySend(message);
|
||||
const message = document.getElementById('message').value;
|
||||
const recipient = document.getElementById('recipient').value;
|
||||
|
||||
await client.sendMessage(message, recipient);
|
||||
displaySend(message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,16 +81,16 @@ async function sendMessageTo() {
|
||||
* @param {string} message
|
||||
*/
|
||||
function displaySend(message) {
|
||||
let timestamp = new Date().toISOString().slice(11, 21);
|
||||
let timestamp = new Date().toISOString().substr(11, 12);
|
||||
|
||||
let sendDiv = document.createElement("div")
|
||||
let paragraph = document.createElement("p")
|
||||
paragraph.setAttribute('style', 'color: blue')
|
||||
let paragraphContent = document.createTextNode(timestamp + " sent >>> " + message)
|
||||
paragraph.appendChild(paragraphContent)
|
||||
let sendDiv = document.createElement('div');
|
||||
let paragraph = document.createElement('p');
|
||||
paragraph.setAttribute('style', 'color: blue');
|
||||
let paragraphContent = document.createTextNode(timestamp + ' sent >>> ' + message);
|
||||
paragraph.appendChild(paragraphContent);
|
||||
|
||||
sendDiv.appendChild(paragraph)
|
||||
document.getElementById("output").appendChild(sendDiv)
|
||||
sendDiv.appendChild(paragraph);
|
||||
document.getElementById('output').appendChild(sendDiv);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,17 +99,17 @@ function displaySend(message) {
|
||||
* @param {string} message
|
||||
*/
|
||||
function displayReceived(message) {
|
||||
const content = message.message
|
||||
const replySurb = message.replySurb
|
||||
const content = message;
|
||||
|
||||
let timestamp = new Date().toISOString().slice(11, 21);
|
||||
let receivedDiv = document.createElement("div")
|
||||
let paragraph = document.createElement("p")
|
||||
paragraph.setAttribute('style', 'color: green')
|
||||
let paragraphContent = document.createTextNode(timestamp + " received >>> " + content)
|
||||
paragraph.appendChild(paragraphContent)
|
||||
receivedDiv.appendChild(paragraph)
|
||||
document.getElementById("output").appendChild(receivedDiv)
|
||||
let timestamp = new Date().toISOString().substr(11, 12);
|
||||
let receivedDiv = document.createElement('div');
|
||||
let paragraph = document.createElement('p');
|
||||
paragraph.setAttribute('style', 'color: green');
|
||||
let paragraphContent = document.createTextNode(timestamp + ' received >>> ' + content);
|
||||
// let paragraphContent = document.createTextNode(timestamp + " received >>> " + content + ((replySurb != null) ? "Reply SURB was attached here (but we can't do anything with it yet" : " (NO REPLY-SURB AVAILABLE)"))
|
||||
paragraph.appendChild(paragraphContent);
|
||||
receivedDiv.appendChild(paragraph);
|
||||
document.getElementById('output').appendChild(receivedDiv);
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +119,7 @@ function displayReceived(message) {
|
||||
* @param {Client} nymClient
|
||||
*/
|
||||
function displaySenderAddress(address) {
|
||||
document.getElementById("sender").value = address;
|
||||
document.getElementById('sender').value = address;
|
||||
}
|
||||
|
||||
// Let's get started!
|
||||
|
||||
+521
-722
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,6 @@
|
||||
"webpack-dev-server": "^4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nymproject/nym-client-wasm": "1.0.0"
|
||||
"@nymproject/nym-client-wasm": "file:../pkg"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,33 @@
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: "./bootstrap.js",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "bootstrap.js",
|
||||
performance: {
|
||||
hints: false,
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000
|
||||
},
|
||||
mode: "development",
|
||||
entry: {
|
||||
bootstrap: './bootstrap.js',
|
||||
worker: './worker.js',
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: '[name].js',
|
||||
},
|
||||
// mode: 'development',
|
||||
mode: 'production',
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({ patterns: ['index.html'] })
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
'index.html',
|
||||
{
|
||||
from: 'node_modules/@nymproject/nym-client-wasm/*.(js|wasm)',
|
||||
to: '[name][ext]',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
],
|
||||
experiments: { syncWebAssembly: true }
|
||||
experiments: { syncWebAssembly: true },
|
||||
};
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright 2020-2022 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
importScripts('nym_client_wasm.js');
|
||||
|
||||
console.log('Initializing worker');
|
||||
|
||||
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
|
||||
const { default_debug, get_gateway, NymClient, set_panic_hook, Config } = wasm_bindgen;
|
||||
|
||||
class ClientWrapper {
|
||||
constructor(config, onMessageHandler) {
|
||||
this.rustClient = new NymClient(config);
|
||||
this.rustClient.set_on_message(onMessageHandler);
|
||||
this.rustClient.set_on_gateway_connect(this.onConnect);
|
||||
}
|
||||
|
||||
selfAddress = () => {
|
||||
return this.rustClient.self_address();
|
||||
};
|
||||
|
||||
onConnect = () => {
|
||||
console.log('Established (and authenticated) gateway connection!');
|
||||
};
|
||||
|
||||
start = async () => {
|
||||
// this is current limitation of wasm in rust - for async methods you can't take self by reference...
|
||||
// I'm trying to figure out if I can somehow hack my way around it, but for time being you have to re-assign
|
||||
// the object (it's the same one)
|
||||
this.rustClient = await this.rustClient.start();
|
||||
};
|
||||
|
||||
sendMessage = async (recipient, message) => {
|
||||
this.rustClient = await this.rustClient.send_message(recipient, message);
|
||||
};
|
||||
|
||||
sendBinaryMessage = async (recipient, message) => {
|
||||
this.rustClient = await this.rustClient.send_binary_message(recipient, message);
|
||||
};
|
||||
}
|
||||
|
||||
let client = null;
|
||||
|
||||
async function main() {
|
||||
// load WASM package
|
||||
await wasm_bindgen('nym_client_wasm_bg.wasm');
|
||||
|
||||
console.log('Loaded WASM');
|
||||
|
||||
// sets up better stack traces in case of in-rust panics
|
||||
set_panic_hook();
|
||||
|
||||
console.error("the current mainnet is not compatible with v2! - either use the pre-merge branch or explicitly set the client to use one of V2 QA networks")
|
||||
return
|
||||
|
||||
// validator server we will use to get topology from
|
||||
// MAINNET (V1):
|
||||
const validator = 'https://validator.nymtech.net/api'; //"http://localhost:8081";
|
||||
const preferredGateway = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
|
||||
// QA (V2):
|
||||
// const validator = 'https://qa-validator-api.nymtech.net/api'; //"http://localhost:8081";
|
||||
// const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM';
|
||||
|
||||
const gatewayEndpoint = await get_gateway(validator, preferredGateway);
|
||||
gatewayEndpoint.gateway_listener = "wss://gateway1.nymtech.net:443"; // this is needed if we want it to work on the web. However this gateway is a v1 gateway, we will need to change for v2 once we get there
|
||||
|
||||
// only really useful if you want to adjust some settings like traffic rate
|
||||
// (if not needed you can just pass a null)
|
||||
const debug = default_debug();
|
||||
|
||||
debug.disable_main_poisson_packet_distribution = true;
|
||||
debug.disable_loop_cover_traffic_stream = true;
|
||||
debug.use_extended_packet_size = true;
|
||||
// debug.average_packet_delay_ms = BigInt(10);
|
||||
// debug.average_ack_delay_ms = BigInt(10);
|
||||
// debug.ack_wait_addition_ms = BigInt(3000);
|
||||
// debug.ack_wait_multiplier = 10;
|
||||
|
||||
debug.topology_refresh_rate_ms = BigInt(60000)
|
||||
|
||||
const config = new Config('my-awesome-wasm-client', validator, gatewayEndpoint, debug);
|
||||
|
||||
const onMessageHandler = (message) => {
|
||||
self.postMessage({
|
||||
kind: 'ReceiveMessage',
|
||||
args: {
|
||||
message,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
console.log('Instantiating WASM client...');
|
||||
client = new ClientWrapper(config, onMessageHandler);
|
||||
console.log('Web worker creating WASM client...');
|
||||
await client.start();
|
||||
console.log('WASM client running!');
|
||||
|
||||
const selfAddress = client.rustClient.self_address();
|
||||
console.log(`Client address is ${selfAddress}`);
|
||||
self.postMessage({
|
||||
kind: 'Ready',
|
||||
args: {
|
||||
selfAddress,
|
||||
},
|
||||
});
|
||||
|
||||
// Set callback to handle messages passed to the worker.
|
||||
self.onmessage = async event => {
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case 'SendMessage': {
|
||||
const { message, recipient } = event.data.args;
|
||||
await client.sendMessage(message, recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Let's get started!
|
||||
main();
|
||||
Generated
-11
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
|
||||
#![allow(clippy::drop_non_drop)]
|
||||
|
||||
use client_core::config::{Debug as ConfigDebug, ExtendedPacketSize, GatewayEndpoint};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Config {
|
||||
/// ID specifies the human readable ID of this particular client.
|
||||
pub(crate) id: String,
|
||||
|
||||
pub(crate) validator_api_url: Url,
|
||||
|
||||
pub(crate) disabled_credentials_mode: bool,
|
||||
|
||||
/// Information regarding how the client should send data to gateway.
|
||||
pub(crate) gateway_endpoint: GatewayEndpoint,
|
||||
|
||||
pub(crate) debug: ConfigDebug,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Config {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
id: String,
|
||||
validator_server: String,
|
||||
gateway_endpoint: GatewayEndpoint,
|
||||
debug: Option<Debug>,
|
||||
) -> Self {
|
||||
Config {
|
||||
id,
|
||||
validator_api_url: validator_server
|
||||
.parse()
|
||||
.expect("provided url was malformed"),
|
||||
disabled_credentials_mode: true,
|
||||
gateway_endpoint,
|
||||
debug: debug.map(Into::into).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// just a helper structure to more easily pass through the JS boundary
|
||||
#[wasm_bindgen]
|
||||
pub struct Debug {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent packet is going to be delayed at any given mix node.
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
pub average_packet_delay_ms: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent acknowledgement is going to be delayed at any given mix node.
|
||||
/// So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
pub average_ack_delay_ms: u64,
|
||||
|
||||
/// Value multiplied with the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 1.
|
||||
pub ack_wait_multiplier: f64,
|
||||
|
||||
/// Value added to the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 0.
|
||||
pub ack_wait_addition_ms: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
pub loop_cover_traffic_average_delay_ms: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
pub message_sending_average_delay_ms: u64,
|
||||
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
pub gateway_response_timeout_ms: u64,
|
||||
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
pub topology_refresh_rate_ms: u64,
|
||||
|
||||
/// During topology refresh, test packets are sent through every single possible network
|
||||
/// path. This timeout determines waiting period until it is decided that the packet
|
||||
/// did not reach its destination.
|
||||
pub topology_resolution_timeout_ms: u64,
|
||||
|
||||
/// Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay_ms])
|
||||
pub disable_loop_cover_traffic_stream: bool,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: bool,
|
||||
}
|
||||
|
||||
impl From<Debug> for ConfigDebug {
|
||||
fn from(debug: Debug) -> Self {
|
||||
// For now we just always use the (older) 32kb extended size
|
||||
let use_extended_packet_size = debug
|
||||
.use_extended_packet_size
|
||||
.then(|| ExtendedPacketSize::Extended32);
|
||||
|
||||
ConfigDebug {
|
||||
average_packet_delay: Duration::from_millis(debug.average_packet_delay_ms),
|
||||
average_ack_delay: Duration::from_millis(debug.average_ack_delay_ms),
|
||||
ack_wait_multiplier: debug.ack_wait_multiplier,
|
||||
ack_wait_addition: Duration::from_millis(debug.ack_wait_addition_ms),
|
||||
loop_cover_traffic_average_delay: Duration::from_millis(
|
||||
debug.loop_cover_traffic_average_delay_ms,
|
||||
),
|
||||
message_sending_average_delay: Duration::from_millis(
|
||||
debug.message_sending_average_delay_ms,
|
||||
),
|
||||
gateway_response_timeout: Duration::from_millis(debug.gateway_response_timeout_ms),
|
||||
topology_refresh_rate: Duration::from_millis(debug.topology_refresh_rate_ms),
|
||||
topology_resolution_timeout: Duration::from_millis(
|
||||
debug.topology_resolution_timeout_ms,
|
||||
),
|
||||
disable_loop_cover_traffic_stream: debug.disable_loop_cover_traffic_stream,
|
||||
disable_main_poisson_packet_distribution: debug
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigDebug> for Debug {
|
||||
fn from(debug: ConfigDebug) -> Self {
|
||||
Debug {
|
||||
average_packet_delay_ms: debug.average_packet_delay.as_millis() as u64,
|
||||
average_ack_delay_ms: debug.average_ack_delay.as_millis() as u64,
|
||||
ack_wait_multiplier: debug.ack_wait_multiplier,
|
||||
ack_wait_addition_ms: debug.ack_wait_addition.as_millis() as u64,
|
||||
loop_cover_traffic_average_delay_ms: debug.loop_cover_traffic_average_delay.as_millis()
|
||||
as u64,
|
||||
message_sending_average_delay_ms: debug.message_sending_average_delay.as_millis()
|
||||
as u64,
|
||||
gateway_response_timeout_ms: debug.gateway_response_timeout.as_millis() as u64,
|
||||
topology_refresh_rate_ms: debug.topology_refresh_rate.as_millis() as u64,
|
||||
topology_resolution_timeout_ms: debug.topology_resolution_timeout.as_millis() as u64,
|
||||
disable_loop_cover_traffic_stream: debug.disable_loop_cover_traffic_stream,
|
||||
disable_main_poisson_packet_distribution: debug
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size: debug.use_extended_packet_size.is_some(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn default_debug() -> Debug {
|
||||
ConfigDebug::default().into()
|
||||
}
|
||||
@@ -1,47 +1,46 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use self::config::Config;
|
||||
use client_core::client::{
|
||||
cover_traffic_stream::LoopCoverTrafficStream,
|
||||
inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender},
|
||||
key_manager::KeyManager,
|
||||
mix_traffic::{BatchMixMessageSender, MixTrafficController},
|
||||
real_messages_control::{self, RealMessagesController},
|
||||
received_buffer::{
|
||||
ReceivedBufferMessage, ReceivedBufferRequestReceiver, ReceivedBufferRequestSender,
|
||||
ReceivedMessagesBufferController,
|
||||
},
|
||||
topology_control::{TopologyAccessor, TopologyRefresher, TopologyRefresherConfig},
|
||||
};
|
||||
use crypto::asymmetric::identity;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::GatewayClient;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use futures::StreamExt;
|
||||
use gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
MixnetMessageSender,
|
||||
};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::preparer::MessagePreparer;
|
||||
use rand::rngs::OsRng;
|
||||
use received_processor::ReceivedMessagesProcessor;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use topology::{gateway, nym_topology_from_detailed, NymTopology};
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use wasm_utils::{console_log, console_warn};
|
||||
|
||||
pub(crate) mod received_processor;
|
||||
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
|
||||
const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200);
|
||||
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
pub mod config;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct NymClient {
|
||||
validator_server: Url,
|
||||
disabled_credentials_mode: bool,
|
||||
config: Config,
|
||||
|
||||
// TODO: technically this doesn't need to be an Arc since wasm is run on a single thread
|
||||
// however, once we eventually combine this code with the native-client's, it will make things
|
||||
// easier.
|
||||
identity: Arc<identity::KeyPair>,
|
||||
encryption_keys: Arc<encryption::KeyPair>,
|
||||
ack_key: Arc<AckKey>,
|
||||
|
||||
message_preparer: Option<MessagePreparer<OsRng>>,
|
||||
// message_receiver: MessageReceiver,
|
||||
/// KeyManager object containing smart pointers to all relevant keys used by the client.
|
||||
key_manager: KeyManager,
|
||||
|
||||
// TODO: this should be stored somewhere persistently
|
||||
// received_keys: HashSet<SURBEncryptionKey>,
|
||||
topology: Option<NymTopology>,
|
||||
gateway_client: Option<GatewayClient>,
|
||||
/// Channel used for transforming 'raw' messages into sphinx packets and sending them
|
||||
/// through the mix network.
|
||||
input_tx: Option<InputMessageSender>,
|
||||
|
||||
// callbacks
|
||||
on_message: Option<js_sys::Function>,
|
||||
@@ -51,31 +50,24 @@ pub struct NymClient {
|
||||
#[wasm_bindgen]
|
||||
impl NymClient {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(validator_server: String) -> Self {
|
||||
let mut rng = OsRng;
|
||||
// for time being generate new keys each time...
|
||||
let identity = identity::KeyPair::new(&mut rng);
|
||||
let encryption_keys = encryption::KeyPair::new(&mut rng);
|
||||
let ack_key = AckKey::new(&mut rng);
|
||||
|
||||
pub fn new(config: Config) -> Self {
|
||||
Self {
|
||||
identity: Arc::new(identity),
|
||||
encryption_keys: Arc::new(encryption_keys),
|
||||
ack_key: Arc::new(ack_key),
|
||||
validator_server: validator_server
|
||||
.parse()
|
||||
.expect("malformed validator server url provided"),
|
||||
message_preparer: None,
|
||||
// received_keys: Default::default(),
|
||||
topology: None,
|
||||
gateway_client: None,
|
||||
|
||||
config,
|
||||
key_manager: Self::setup_key_manager(),
|
||||
on_message: None,
|
||||
on_gateway_connect: None,
|
||||
disabled_credentials_mode: true,
|
||||
input_tx: None,
|
||||
}
|
||||
}
|
||||
|
||||
// perhaps this should be public?
|
||||
fn setup_key_manager() -> KeyManager {
|
||||
let mut rng = OsRng;
|
||||
// for time being generate new keys each time...
|
||||
console_log!("generated new set of keys");
|
||||
KeyManager::new(&mut rng)
|
||||
}
|
||||
|
||||
pub fn set_on_message(&mut self, on_message: js_sys::Function) {
|
||||
self.on_message = Some(on_message);
|
||||
}
|
||||
@@ -85,62 +77,137 @@ impl NymClient {
|
||||
self.on_gateway_connect = Some(on_connect)
|
||||
}
|
||||
|
||||
pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) {
|
||||
console_log!(
|
||||
"Setting disabled credentials mode to {}",
|
||||
disabled_credentials_mode
|
||||
);
|
||||
self.disabled_credentials_mode = disabled_credentials_mode;
|
||||
}
|
||||
|
||||
fn self_recipient(&self) -> Recipient {
|
||||
fn as_mix_recipient(&self) -> Recipient {
|
||||
Recipient::new(
|
||||
*self.identity.public_key(),
|
||||
*self.encryption_keys.public_key(),
|
||||
self.gateway_client
|
||||
.as_ref()
|
||||
.expect("gateway connection was not established!")
|
||||
.gateway_identity(),
|
||||
*self.key_manager.identity_keypair().public_key(),
|
||||
*self.key_manager.encryption_keypair().public_key(),
|
||||
identity::PublicKey::from_base58_string(&self.config.gateway_endpoint.gateway_id)
|
||||
.expect("no gateway has been selected"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn self_address(&self) -> String {
|
||||
self.self_recipient().to_string()
|
||||
self.as_mix_recipient().to_string()
|
||||
}
|
||||
|
||||
// Right now it's impossible to have async exported functions to take `&self` rather than self
|
||||
pub async fn initial_setup(self) -> Self {
|
||||
let disabled_credentials_mode = self.disabled_credentials_mode;
|
||||
// future constantly pumping loop cover traffic at some specified average rate
|
||||
// the pumped traffic goes to the MixTrafficController
|
||||
fn start_cover_traffic_stream(
|
||||
&self,
|
||||
topology_accessor: TopologyAccessor,
|
||||
mix_tx: BatchMixMessageSender,
|
||||
) {
|
||||
console_log!("Starting loop cover traffic stream...");
|
||||
|
||||
let bandwidth_controller = None;
|
||||
|
||||
let mut client = self.get_and_update_topology().await;
|
||||
let gateway = client.choose_gateway();
|
||||
|
||||
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway.clients_address(),
|
||||
Arc::clone(&client.identity),
|
||||
gateway.identity_key,
|
||||
gateway.owner.clone(),
|
||||
None,
|
||||
mixnet_messages_sender,
|
||||
ack_sender,
|
||||
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
bandwidth_controller,
|
||||
let mut stream = LoopCoverTrafficStream::new(
|
||||
self.key_manager.ack_key(),
|
||||
self.config.debug.average_ack_delay,
|
||||
self.config.debug.average_packet_delay,
|
||||
self.config.debug.loop_cover_traffic_average_delay,
|
||||
mix_tx,
|
||||
self.as_mix_recipient(),
|
||||
topology_accessor,
|
||||
);
|
||||
|
||||
gateway_client.set_disabled_credentials_mode(disabled_credentials_mode);
|
||||
if let Some(size) = &self.config.debug.use_extended_packet_size {
|
||||
stream.set_custom_packet_size(size.clone().into());
|
||||
}
|
||||
|
||||
gateway_client
|
||||
stream.start();
|
||||
}
|
||||
|
||||
fn start_real_traffic_controller(
|
||||
&self,
|
||||
topology_accessor: TopologyAccessor,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
input_receiver: InputMessageReceiver,
|
||||
mix_sender: BatchMixMessageSender,
|
||||
) {
|
||||
let mut controller_config = real_messages_control::Config::new(
|
||||
self.key_manager.ack_key(),
|
||||
self.config.debug.ack_wait_multiplier,
|
||||
self.config.debug.ack_wait_addition,
|
||||
self.config.debug.average_ack_delay,
|
||||
self.config.debug.message_sending_average_delay,
|
||||
self.config.debug.average_packet_delay,
|
||||
self.config.debug.disable_main_poisson_packet_distribution,
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
if let Some(size) = &self.config.debug.use_extended_packet_size {
|
||||
controller_config.set_custom_packet_size(size.clone().into());
|
||||
}
|
||||
|
||||
console_log!("Starting real traffic stream...");
|
||||
|
||||
RealMessagesController::new(
|
||||
controller_config,
|
||||
ack_receiver,
|
||||
input_receiver,
|
||||
mix_sender,
|
||||
topology_accessor,
|
||||
)
|
||||
.start();
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
// required so that other components would be able to use them (say the websocket)
|
||||
fn start_received_messages_buffer_controller(
|
||||
&self,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
mixnet_receiver: MixnetMessageReceiver,
|
||||
) {
|
||||
console_log!("Starting received messages buffer controller...");
|
||||
ReceivedMessagesBufferController::new(
|
||||
self.key_manager.encryption_keypair(),
|
||||
query_receiver,
|
||||
mixnet_receiver,
|
||||
)
|
||||
.start()
|
||||
}
|
||||
|
||||
async fn start_gateway_client(
|
||||
&mut self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
) -> GatewayClient {
|
||||
let gateway_id = self.config.gateway_endpoint.gateway_id.clone();
|
||||
if gateway_id.is_empty() {
|
||||
panic!("The identity of the gateway is unknown - did you run `get_gateway()`?")
|
||||
}
|
||||
let gateway_owner = self.config.gateway_endpoint.gateway_owner.clone();
|
||||
if gateway_owner.is_empty() {
|
||||
panic!("The owner of the gateway is unknown - did you run `get_gateway()`?")
|
||||
}
|
||||
let gateway_address = self.config.gateway_endpoint.gateway_listener.clone();
|
||||
if gateway_address.is_empty() {
|
||||
panic!("The address of the gateway is unknown - did you run `get_gateway()`?")
|
||||
}
|
||||
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.expect("provided gateway id is invalid!");
|
||||
|
||||
let mut gateway_client = GatewayClient::new(
|
||||
gateway_address,
|
||||
self.key_manager.identity_keypair(),
|
||||
gateway_identity,
|
||||
gateway_owner,
|
||||
None,
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.config.debug.gateway_response_timeout,
|
||||
None,
|
||||
);
|
||||
|
||||
gateway_client.set_disabled_credentials_mode(self.config.disabled_credentials_mode);
|
||||
|
||||
let shared_keys = gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
self.key_manager.insert_gateway_shared_key(shared_keys);
|
||||
|
||||
client.gateway_client = Some(gateway_client);
|
||||
match client.on_gateway_connect.as_ref() {
|
||||
match self.on_gateway_connect.as_ref() {
|
||||
Some(callback) => {
|
||||
callback
|
||||
.call0(&JsValue::null())
|
||||
@@ -149,124 +216,166 @@ impl NymClient {
|
||||
None => console_log!("Gateway connection established - no callback specified"),
|
||||
};
|
||||
|
||||
let rng = rand::rngs::OsRng;
|
||||
let message_preparer = MessagePreparer::new(
|
||||
rng,
|
||||
client.self_recipient(),
|
||||
DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
DEFAULT_AVERAGE_ACK_DELAY,
|
||||
gateway_client
|
||||
}
|
||||
|
||||
// future responsible for periodically polling directory server and updating
|
||||
// the current global view of topology
|
||||
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
vec![self.config.validator_api_url.clone()],
|
||||
self.config.debug.topology_refresh_rate,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
);
|
||||
let mut topology_refresher =
|
||||
TopologyRefresher::new(topology_refresher_config, topology_accessor);
|
||||
// before returning, block entire runtime to refresh the current network view so that any
|
||||
// components depending on topology would see a non-empty view
|
||||
console_log!("Obtaining initial network topology");
|
||||
topology_refresher.refresh().await;
|
||||
|
||||
let received_processor = ReceivedMessagesProcessor::new(
|
||||
Arc::clone(&client.encryption_keys),
|
||||
Arc::clone(&client.ack_key),
|
||||
);
|
||||
// TODO: a slightly more graceful termination here
|
||||
if !topology_refresher.is_topology_routable().await {
|
||||
panic!(
|
||||
"The current network topology seem to be insufficient to route any packets through\
|
||||
- check if enough nodes and a gateway are online"
|
||||
);
|
||||
}
|
||||
|
||||
client.message_preparer = Some(message_preparer);
|
||||
console_log!("Starting topology refresher...");
|
||||
|
||||
spawn_local(received_processor.start_processing(
|
||||
// TODO: re-enable
|
||||
topology_refresher.start();
|
||||
}
|
||||
|
||||
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
|
||||
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
|
||||
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
|
||||
// requests?
|
||||
fn start_mix_traffic_controller(gateway_client: GatewayClient) -> BatchMixMessageSender {
|
||||
console_log!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
|
||||
mix_traffic_controller.start();
|
||||
mix_tx
|
||||
}
|
||||
|
||||
// TODO: this procedure is extremely overcomplicated, because it's based off native client's behaviour
|
||||
// which doesn't fully apply in this case
|
||||
fn start_reconstructed_pusher(
|
||||
&mut self,
|
||||
received_buffer_request_sender: ReceivedBufferRequestSender,
|
||||
) {
|
||||
let on_message = self.on_message.take();
|
||||
|
||||
spawn_local(async move {
|
||||
let (reconstructed_sender, mut reconstructed_receiver) = mpsc::unbounded();
|
||||
|
||||
// tell the buffer to start sending stuff to us
|
||||
received_buffer_request_sender
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
|
||||
reconstructed_sender,
|
||||
))
|
||||
.expect("the buffer request failed!");
|
||||
|
||||
let this = JsValue::null();
|
||||
|
||||
while let Some(reconstructed) = reconstructed_receiver.next().await {
|
||||
if let Some(ref callback) = on_message {
|
||||
for msg in reconstructed {
|
||||
if msg.reply_surb.is_some() {
|
||||
console_log!("the received message contained a reply-surb that we do not know how to handle (yet)")
|
||||
}
|
||||
let stringified = String::from_utf8_lossy(&msg.message).into_owned();
|
||||
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)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn start(mut self) -> NymClient {
|
||||
console_log!("Starting wasm client '{}'", self.config.id);
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
// rather than creating them here, so say for example the buffer controller would create the request channels
|
||||
// and would allow anyone to clone the sender channel
|
||||
|
||||
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
|
||||
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
|
||||
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
|
||||
|
||||
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
|
||||
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
|
||||
|
||||
// channels responsible for controlling real messages
|
||||
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
|
||||
|
||||
// channels responsible for controlling ack messages
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
let shared_topology_accessor = TopologyAccessor::new();
|
||||
|
||||
// the components are started in very specific order. Unless you know what you are doing,
|
||||
// do not change that.
|
||||
self.start_topology_refresher(shared_topology_accessor.clone())
|
||||
.await;
|
||||
self.start_received_messages_buffer_controller(
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
ack_receiver,
|
||||
client.on_message.take().expect("on_message was not set!"),
|
||||
));
|
||||
);
|
||||
|
||||
client
|
||||
let gateway_client = self
|
||||
.start_gateway_client(mixnet_messages_sender, ack_sender)
|
||||
.await;
|
||||
|
||||
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
|
||||
// that are to be sent to the mixnet. They are used by cover traffic stream and real
|
||||
// traffic stream.
|
||||
// The MixTrafficController then sends the actual traffic
|
||||
let sphinx_message_sender = Self::start_mix_traffic_controller(gateway_client);
|
||||
|
||||
self.start_real_traffic_controller(
|
||||
shared_topology_accessor.clone(),
|
||||
ack_receiver,
|
||||
input_receiver,
|
||||
sphinx_message_sender.clone(),
|
||||
);
|
||||
|
||||
if !self.config.debug.disable_loop_cover_traffic_stream {
|
||||
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
|
||||
}
|
||||
|
||||
self.start_reconstructed_pusher(received_buffer_request_sender);
|
||||
self.input_tx = Some(input_sender);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
// Right now it's impossible to have async exported functions to take `&mut self` rather than mut self
|
||||
// TODO: try Rc<RefCell<Self>> approach?
|
||||
pub async fn send_message(mut self, message: String, recipient: String) -> Self {
|
||||
pub async fn send_message(self, message: String, recipient: String) -> Self {
|
||||
console_log!("Sending {} to {}", message, recipient);
|
||||
|
||||
let message_bytes = message.into_bytes();
|
||||
self.send_binary_message(message_bytes, recipient).await
|
||||
}
|
||||
|
||||
pub async fn send_binary_message(self, message: Vec<u8>, recipient: String) -> Self {
|
||||
console_log!("Sending {} bytes to {}", message.len(), recipient);
|
||||
|
||||
let recipient = Recipient::try_from_base58_string(recipient).unwrap();
|
||||
|
||||
let topology = self
|
||||
.topology
|
||||
let input_msg = InputMessage::new_fresh(recipient, message, false);
|
||||
|
||||
self.input_tx
|
||||
.as_ref()
|
||||
.expect("did not obtain topology before");
|
||||
|
||||
let message_preparer = self.message_preparer.as_mut().unwrap();
|
||||
|
||||
let (split_message, _reply_keys) = message_preparer
|
||||
.prepare_and_split_message(message_bytes, false, topology)
|
||||
.expect("failed to split the message");
|
||||
|
||||
let mut mix_packets = Vec::with_capacity(split_message.len());
|
||||
for message_chunk in split_message {
|
||||
// don't bother with acks etc. for time being
|
||||
let prepared_fragment = message_preparer
|
||||
.prepare_chunk_for_sending(message_chunk, topology, &self.ack_key, &recipient)
|
||||
.unwrap();
|
||||
|
||||
console_warn!("packet is going to have round trip time of {:?}, but we're not going to do anything for acks anyway ", prepared_fragment.total_delay);
|
||||
mix_packets.push(prepared_fragment.mix_packet);
|
||||
}
|
||||
self.gateway_client
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.batch_send_mix_packets(mix_packets)
|
||||
.await
|
||||
.expect("start method was not called before!")
|
||||
.unbounded_send(input_msg)
|
||||
.unwrap();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn choose_gateway(&self) -> &gateway::Node {
|
||||
let topology = self
|
||||
.topology
|
||||
.as_ref()
|
||||
.expect("did not obtain topology before");
|
||||
|
||||
// choose the first one available
|
||||
assert!(!topology.gateways().is_empty());
|
||||
topology.gateways().first().unwrap()
|
||||
}
|
||||
|
||||
// Right now it's impossible to have async exported functions to take `&mut self` rather than mut self
|
||||
// self: Rc<Self>
|
||||
// or this: Rc<RefCell<Self>>
|
||||
pub async fn get_and_update_topology(mut self) -> Self {
|
||||
let new_topology = self.get_nym_topology().await;
|
||||
self.update_topology(new_topology);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn update_topology(&mut self, topology: NymTopology) {
|
||||
self.topology = Some(topology)
|
||||
}
|
||||
|
||||
// when updated to 0.10.0, to prevent headache later on, this function requires those two imports:
|
||||
// use js_sys::Promise;
|
||||
// use wasm_bindgen_futures::future_to_promise;
|
||||
//
|
||||
// pub fn get_full_topology_json(&self) -> Promise {
|
||||
// let validator_client_config = validator_client::Config::new(
|
||||
// vec![self.validator_server.clone()],
|
||||
// &self.mixnet_contract_address,
|
||||
// );
|
||||
// let validator_client = validator_client::Client::new(validator_client_config);
|
||||
//
|
||||
// future_to_promise(async move {
|
||||
// let topology = &validator_client.get_active_topology().await.unwrap();
|
||||
// Ok(JsValue::from_serde(&topology).unwrap())
|
||||
// })
|
||||
// }
|
||||
|
||||
pub(crate) async fn get_nym_topology(&self) -> NymTopology {
|
||||
let validator_client = validator_client::ApiClient::new(self.validator_server.clone());
|
||||
|
||||
let mixnodes = match validator_client.get_cached_active_mixnodes().await {
|
||||
Err(err) => panic!("{:?}", err),
|
||||
Ok(mixes) => mixes,
|
||||
};
|
||||
|
||||
let gateways = match validator_client.get_cached_gateways().await {
|
||||
Err(err) => panic!("{}", err),
|
||||
Ok(gateways) => gateways,
|
||||
};
|
||||
|
||||
let topology = nym_topology_from_detailed(mixnodes, gateways);
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
topology.filter_system_version(version)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crypto::asymmetric::encryption;
|
||||
use futures::StreamExt;
|
||||
use gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
|
||||
use nymsphinx::acknowledgements::identifier::recover_identifier;
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
||||
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::{console_error, console_log, console_warn};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ProcessedMessage {
|
||||
pub message: String,
|
||||
pub reply_surb: Option<String>,
|
||||
}
|
||||
|
||||
impl From<ReconstructedMessage> for ProcessedMessage {
|
||||
fn from(reconstructed: ReconstructedMessage) -> Self {
|
||||
ProcessedMessage {
|
||||
message: String::from_utf8_lossy(&reconstructed.message).into_owned(),
|
||||
reply_surb: reconstructed
|
||||
.reply_surb
|
||||
.map(|reply_surb| reply_surb.to_base58_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ReceivedMessagesProcessor {
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
ack_key: Arc<AckKey>,
|
||||
message_receiver: MessageReceiver,
|
||||
|
||||
recently_reconstructed: HashSet<i32>,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesProcessor {
|
||||
pub(crate) fn new(
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
ack_key: Arc<AckKey>,
|
||||
) -> Self {
|
||||
ReceivedMessagesProcessor {
|
||||
local_encryption_keypair,
|
||||
ack_key,
|
||||
message_receiver: MessageReceiver::new(),
|
||||
recently_reconstructed: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: duplicate code from received_buffer.rs in client-core....
|
||||
fn process_received_fragment(&mut self, raw_fragment: Vec<u8>) -> Option<ProcessedMessage> {
|
||||
let fragment_data = match self
|
||||
.message_receiver
|
||||
.recover_plaintext(self.local_encryption_keypair.private_key(), raw_fragment)
|
||||
{
|
||||
Err(e) => {
|
||||
console_warn!("failed to recover fragment data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e);
|
||||
return None;
|
||||
}
|
||||
Ok(frag_data) => frag_data,
|
||||
};
|
||||
|
||||
if nymsphinx::cover::is_cover(&fragment_data) {
|
||||
// currently won't be the case for a loooong time
|
||||
console_log!("The message was a loop cover message! Skipping it");
|
||||
return None;
|
||||
}
|
||||
|
||||
let fragment = match self.message_receiver.recover_fragment(&fragment_data) {
|
||||
Err(e) => {
|
||||
console_warn!("failed to recover fragment from raw data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e);
|
||||
return None;
|
||||
}
|
||||
Ok(frag) => frag,
|
||||
};
|
||||
|
||||
if self.recently_reconstructed.contains(&fragment.id()) {
|
||||
console_warn!("Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost", fragment.id());
|
||||
return None;
|
||||
}
|
||||
|
||||
// if we returned an error the underlying message is malformed in some way
|
||||
match self.message_receiver.insert_new_fragment(fragment) {
|
||||
Err(err) => match err {
|
||||
MessageRecoveryError::MalformedReconstructedMessage(message_sets) => {
|
||||
// TODO: should we really insert reconstructed sets? could this be abused for some attack?
|
||||
for set_id in message_sets {
|
||||
if !self.recently_reconstructed.insert(set_id) {
|
||||
// or perhaps we should even panic at this point?
|
||||
console_error!(
|
||||
"Reconstructed another message containing already used set id!"
|
||||
)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => unreachable!(
|
||||
"no other error kind should have been returned here! If so, it's a bug!"
|
||||
),
|
||||
},
|
||||
Ok(reconstruction_result) => match reconstruction_result {
|
||||
Some((reconstructed_message, used_sets)) => {
|
||||
for set_id in used_sets {
|
||||
if !self.recently_reconstructed.insert(set_id) {
|
||||
// or perhaps we should even panic at this point?
|
||||
console_error!(
|
||||
"Reconstructed another message containing already used set id!"
|
||||
)
|
||||
}
|
||||
}
|
||||
Some(reconstructed_message.into())
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: duplicate code from acknowledgement listener...
|
||||
fn process_received_ack(&self, ack_content: Vec<u8>) {
|
||||
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
|
||||
.map(FragmentIdentifier::try_from_bytes)
|
||||
{
|
||||
Some(Ok(frag_id)) => frag_id,
|
||||
_ => {
|
||||
console_warn!("Received invalid ACK!"); // should we do anything else about that?
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// if we received an ack for cover message or a reply there will be nothing to remove,
|
||||
// because nothing was inserted in the first place
|
||||
if frag_id == COVER_FRAG_ID {
|
||||
return;
|
||||
} else if frag_id.is_reply() {
|
||||
console_warn!("Received an ack for a reply message - no need to do anything! (don't know what to do!)");
|
||||
// TODO: probably there will need to be some extra procedure here, something to notify
|
||||
// user that his reply reached the recipient (since we got an ack)
|
||||
return;
|
||||
}
|
||||
|
||||
console_log!(
|
||||
"Received an ack for fragment {:?} but can't do anything more about it just yet...",
|
||||
frag_id
|
||||
);
|
||||
|
||||
// here be ack handling...
|
||||
}
|
||||
|
||||
// TODO: this needs to have a shutdown signal!
|
||||
pub(crate) async fn start_processing(
|
||||
mut self,
|
||||
mixnet_messages_receiver: MixnetMessageReceiver,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
on_message: js_sys::Function,
|
||||
) {
|
||||
let mut fused_mixnet_messages_receiver = mixnet_messages_receiver.fuse();
|
||||
let mut fused_ack_receiver = ack_receiver.fuse();
|
||||
let this = JsValue::null();
|
||||
|
||||
loop {
|
||||
futures::select! {
|
||||
mix_msgs = fused_mixnet_messages_receiver.next() => {
|
||||
for mix_msg in mix_msgs.unwrap() {
|
||||
if let Some(processed) = self.process_received_fragment(mix_msg) {
|
||||
let arg1 = JsValue::from_serde(&processed).unwrap();
|
||||
on_message.call1(&this, &arg1).expect("on message failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
acks = fused_ack_receiver.next() => {
|
||||
for ack in acks.unwrap() {
|
||||
self.process_received_ack(ack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use client_core::config::GatewayEndpoint;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpoint {
|
||||
let validator_client = validator_client::ApiClient::new(api_server.parse().unwrap());
|
||||
|
||||
let gateways = match validator_client.get_cached_gateways().await {
|
||||
Err(err) => panic!("failed to obtain list of all gateways - {}", err),
|
||||
Ok(gateways) => gateways,
|
||||
};
|
||||
|
||||
if let Some(preferred) = preferred {
|
||||
if let Some(details) = gateways
|
||||
.iter()
|
||||
.find(|g| g.gateway.identity_key == preferred)
|
||||
{
|
||||
return GatewayEndpoint {
|
||||
gateway_id: details.gateway.identity_key.clone(),
|
||||
gateway_owner: details.owner.to_string(),
|
||||
gateway_listener: format!(
|
||||
"ws://{}:{}",
|
||||
details.gateway.host, details.gateway.clients_port
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let details = gateways
|
||||
.first()
|
||||
.expect("current topology holds no gateways");
|
||||
|
||||
GatewayEndpoint {
|
||||
gateway_id: details.gateway.identity_key.clone(),
|
||||
gateway_owner: details.owner.to_string(),
|
||||
gateway_listener: format!(
|
||||
"ws://{}:{}",
|
||||
details.gateway.host, details.gateway.clients_port
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod client;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod gateway_selector;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn set_panic_hook() {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
@@ -14,9 +14,8 @@ log = "0.4"
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
secp256k1 = { version = "0.20.3", optional = true }
|
||||
web3 = { version = "0.17.0", default-features = false, optional = true }
|
||||
async-trait = { version = "0.1.51" }
|
||||
tokio = { version = "1.21.2", features = ["macros"] }
|
||||
|
||||
# internal
|
||||
coconut-interface = { path = "../../coconut-interface", optional = true }
|
||||
@@ -28,13 +27,14 @@ nymsphinx = { path = "../../nymsphinx" }
|
||||
pemstore = { path = "../../pemstore" }
|
||||
validator-client = { path = "../validator-client", optional = true }
|
||||
|
||||
|
||||
[dependencies.tungstenite]
|
||||
version = "0.13"
|
||||
default-features = false
|
||||
|
||||
# non-wasm-only dependencies
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
version = "1.19.1"
|
||||
version = "1.21.2"
|
||||
features = ["macros", "rt", "net", "sync", "time"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
|
||||
@@ -61,8 +61,9 @@ version = "0.4"
|
||||
path = "../../wasm-utils"
|
||||
|
||||
# only import it in wasm. Prefer proper tokio timer in non-wasm
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.fluvio-wasm-timer]
|
||||
version = "0.2.5"
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-timer]
|
||||
git = "https://github.com/mmsinclair/wasm-timer"
|
||||
rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"
|
||||
|
||||
# this is due to tungstenite using `rand` 0.8 and associated changes in `getrandom` crate
|
||||
# which now does not support wasm32-unknown-unknown target by default.
|
||||
@@ -80,4 +81,3 @@ features = ["js"]
|
||||
[features]
|
||||
coconut = ["gateway-requests/coconut", "coconut-interface", "validator-client", "credentials/coconut"]
|
||||
wasm = []
|
||||
default = ["web3/default", "secp256k1"]
|
||||
|
||||
@@ -36,7 +36,7 @@ use tungstenite::protocol::Message;
|
||||
use tokio_tungstenite::connect_async;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use fluvio_wasm_timer as wasm_timer;
|
||||
use wasm_timer;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ use thiserror::Error;
|
||||
use tungstenite::Error as WsError;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::JsValue;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use web3::{contract::Error as Web3ContractError, Error as Web3Error};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GatewayClientError {
|
||||
@@ -37,18 +35,6 @@ pub enum GatewayClientError {
|
||||
#[error("There was a network error")]
|
||||
NetworkErrorWasm(JsValue),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Could not burn ERC20 token in Ethereum smart contract - {0}")]
|
||||
BurnTokenError(#[from] Web3Error),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Could not run web3 contract - {0}")]
|
||||
Web3ContractError(#[from] Web3ContractError),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Invalid Ethereum private key")]
|
||||
InvalidEthereumPrivateKey,
|
||||
|
||||
#[error("Invalid URL - {0}")]
|
||||
InvalidURL(String),
|
||||
|
||||
|
||||
@@ -25,3 +25,15 @@ pub(crate) fn cleanup_socket_message(
|
||||
None => Err(GatewayClientError::ConnectionAbruptlyClosed),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup_socket_messages(
|
||||
msgs: Option<Vec<Result<Message, WsError>>>,
|
||||
) -> Result<Vec<Message>, GatewayClientError> {
|
||||
match msgs {
|
||||
Some(msgs) => msgs
|
||||
.into_iter()
|
||||
.map(|msg| msg.map_err(GatewayClientError::NetworkError))
|
||||
.collect(),
|
||||
None => Err(GatewayClientError::ConnectionAbruptlyClosed),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +59,22 @@ impl PacketRouter {
|
||||
} else if received_packet.len()
|
||||
== PacketSize::RegularPacket.plaintext_size() - ack_overhead
|
||||
{
|
||||
trace!("routing regular packet");
|
||||
received_messages.push(received_packet);
|
||||
} else if received_packet.len()
|
||||
== PacketSize::ExtendedPacket.plaintext_size() - ack_overhead
|
||||
== PacketSize::ExtendedPacket8.plaintext_size() - ack_overhead
|
||||
{
|
||||
warn!("received extended packet? Did not expect this...");
|
||||
trace!("routing extended8 packet");
|
||||
received_messages.push(received_packet);
|
||||
} else if received_packet.len()
|
||||
== PacketSize::ExtendedPacket16.plaintext_size() - ack_overhead
|
||||
{
|
||||
trace!("routing extended16 packet");
|
||||
received_messages.push(received_packet);
|
||||
} else if received_packet.len()
|
||||
== PacketSize::ExtendedPacket32.plaintext_size() - ack_overhead
|
||||
{
|
||||
trace!("routing extended32 packet");
|
||||
received_messages.push(received_packet);
|
||||
} else {
|
||||
// this can happen if other clients are not padding their messages
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::cleanup_socket_message;
|
||||
use crate::cleanup_socket_messages;
|
||||
use crate::error::GatewayClientError;
|
||||
use crate::packet_router::PacketRouter;
|
||||
use futures::channel::oneshot;
|
||||
use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
use gateway_requests::BinaryResponse;
|
||||
use log::*;
|
||||
@@ -44,16 +44,15 @@ pub(crate) struct PartiallyDelegated {
|
||||
}
|
||||
|
||||
impl PartiallyDelegated {
|
||||
fn route_socket_message(
|
||||
ws_msg: Message,
|
||||
packet_router: &mut PacketRouter,
|
||||
shared_key: &SharedKeys,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
match ws_msg {
|
||||
Message::Binary(bin_msg) => {
|
||||
// this function decrypts the request and checks the MAC
|
||||
let plaintext =
|
||||
match BinaryResponse::try_from_encrypted_tagged_bytes(bin_msg, shared_key) {
|
||||
fn recover_received_plaintexts(ws_msgs: Vec<Message>, shared_key: &SharedKeys) -> Vec<Vec<u8>> {
|
||||
let mut plaintexts = Vec::with_capacity(ws_msgs.len());
|
||||
for ws_msg in ws_msgs {
|
||||
match ws_msg {
|
||||
Message::Binary(bin_msg) => {
|
||||
// this function decrypts the request and checks the MAC
|
||||
let plaintext = match BinaryResponse::try_from_encrypted_tagged_bytes(
|
||||
bin_msg, shared_key,
|
||||
) {
|
||||
Ok(bin_response) => match bin_response {
|
||||
BinaryResponse::PushedMixMessage(plaintext) => plaintext,
|
||||
},
|
||||
@@ -62,29 +61,37 @@ impl PartiallyDelegated {
|
||||
"message received from the gateway was malformed! - {:?}",
|
||||
err
|
||||
);
|
||||
return Ok(());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
plaintexts.push(plaintext)
|
||||
}
|
||||
// I think that in the future we should perhaps have some sequence number system, i.e.
|
||||
// so each request/response pair can be easily identified, so that if messages are
|
||||
// not ordered (for some peculiar reason) we wouldn't lose anything.
|
||||
// This would also require NOT discarding any text responses here.
|
||||
|
||||
// TODO: some batching mechanism to allow reading and sending more than
|
||||
// one packet at the time, because the receiver can easily handle it
|
||||
packet_router.route_received(vec![plaintext])
|
||||
}
|
||||
// I think that in the future we should perhaps have some sequence number system, i.e.
|
||||
// so each request/response pair can be easily identified, so that if messages are
|
||||
// not ordered (for some peculiar reason) we wouldn't lose anything.
|
||||
// This would also require NOT discarding any text responses here.
|
||||
|
||||
// TODO: those can return the "send confirmations" - perhaps it should be somehow worked around?
|
||||
Message::Text(text) => {
|
||||
trace!(
|
||||
// TODO: those can return the "send confirmations" - perhaps it should be somehow worked around?
|
||||
Message::Text(text) => {
|
||||
trace!(
|
||||
"received a text message - probably a response to some previous query! - {}",
|
||||
text
|
||||
);
|
||||
Ok(())
|
||||
continue;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
plaintexts
|
||||
}
|
||||
|
||||
fn route_socket_messages(
|
||||
ws_msgs: Vec<Message>,
|
||||
packet_router: &mut PacketRouter,
|
||||
shared_key: &SharedKeys,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
let plaintexts = Self::recover_received_plaintexts(ws_msgs, shared_key);
|
||||
packet_router.route_received(plaintexts)
|
||||
}
|
||||
|
||||
pub(crate) fn split_and_listen_for_mixnet_messages(
|
||||
@@ -101,47 +108,46 @@ impl PartiallyDelegated {
|
||||
let (sink, mut stream) = conn.split();
|
||||
|
||||
let mixnet_receiver_future = async move {
|
||||
let mut fused_receiver = notify_receiver.fuse();
|
||||
let mut fused_stream = (&mut stream).fuse();
|
||||
let mut notify_receiver = notify_receiver;
|
||||
let mut chunk_stream = (&mut stream).ready_chunks(8);
|
||||
let mut packet_router = packet_router;
|
||||
|
||||
// Bit of an ugly workaround for selecting on an `Option` without having access to
|
||||
// `tokio::select`
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let shutdown = {
|
||||
let m_shutdown = shutdown.clone();
|
||||
async {
|
||||
if let Some(mut s) = m_shutdown {
|
||||
if let Some(mut s) = shutdown {
|
||||
s.recv().await
|
||||
} else {
|
||||
std::future::pending::<()>().await
|
||||
}
|
||||
}
|
||||
.fuse()
|
||||
};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::pin!(shutdown);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let mut shutdown = std::future::pending::<()>().fuse();
|
||||
let mut shutdown = std::future::pending::<()>();
|
||||
|
||||
let ret_err = loop {
|
||||
futures::select! {
|
||||
_ = shutdown => {
|
||||
tokio::select! {
|
||||
_ = &mut shutdown => {
|
||||
log::trace!("GatewayClient listener: Received shutdown");
|
||||
log::debug!("GatewayClient listener: Exiting");
|
||||
return;
|
||||
}
|
||||
_ = &mut fused_receiver => {
|
||||
_ = &mut notify_receiver => {
|
||||
break Ok(());
|
||||
}
|
||||
msg = fused_stream.next() => {
|
||||
let ws_msg = match cleanup_socket_message(msg) {
|
||||
msgs = chunk_stream.next() => {
|
||||
let ws_msgs = match cleanup_socket_messages(msgs) {
|
||||
Err(err) => break Err(err),
|
||||
Ok(msg) => msg
|
||||
Ok(msgs) => msgs
|
||||
};
|
||||
if let Err(err) = Self::route_socket_message(ws_msg, &mut packet_router, shared_key.as_ref()) {
|
||||
log::warn!("Route socket message failed: {:?}", err);
|
||||
|
||||
if let Err(err) = Self::route_socket_messages(ws_msgs, &mut packet_router, shared_key.as_ref()) {
|
||||
log::warn!("Route socket messages failed: {:?}", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
log = "0.4.8"
|
||||
tokio = { version = "1.19.1", features = ["time", "net", "rt"] }
|
||||
tokio = { version = "1.21.2", features = ["time", "net", "rt"] }
|
||||
tokio-util = { version = "0.7.3", features = ["codec"] }
|
||||
|
||||
# internal
|
||||
|
||||
@@ -23,6 +23,7 @@ pub struct Config {
|
||||
maximum_reconnection_backoff: Duration,
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
use_legacy_version: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -31,12 +32,14 @@ impl Config {
|
||||
maximum_reconnection_backoff: Duration,
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
use_legacy_version: bool,
|
||||
) -> Self {
|
||||
Config {
|
||||
initial_reconnection_backoff,
|
||||
maximum_reconnection_backoff,
|
||||
initial_connection_timeout,
|
||||
maximum_connection_buffer_size,
|
||||
use_legacy_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,7 +204,8 @@ impl SendWithoutResponse for Client {
|
||||
packet_mode: PacketMode,
|
||||
) -> io::Result<()> {
|
||||
trace!("Sending packet to {:?}", address);
|
||||
let framed_packet = FramedSphinxPacket::new(packet, packet_mode);
|
||||
let framed_packet =
|
||||
FramedSphinxPacket::new(packet, packet_mode, self.config.use_legacy_version);
|
||||
|
||||
if let Some(sender) = self.conn_new.get_mut(&address) {
|
||||
if let Err(err) = sender.channel.try_send(framed_packet) {
|
||||
@@ -259,6 +263,7 @@ mod tests {
|
||||
maximum_reconnection_backoff: Duration::from_millis(300_000),
|
||||
initial_connection_timeout: Duration::from_millis(1_500),
|
||||
maximum_connection_buffer_size: 128,
|
||||
use_legacy_version: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -24,12 +24,14 @@ impl PacketForwarder {
|
||||
maximum_reconnection_backoff: Duration,
|
||||
initial_connection_timeout: Duration,
|
||||
maximum_connection_buffer_size: usize,
|
||||
use_legacy_version: bool,
|
||||
) -> (PacketForwarder, MixForwardingSender) {
|
||||
let client_config = Config::new(
|
||||
initial_reconnection_backoff,
|
||||
maximum_reconnection_backoff,
|
||||
initial_connection_timeout,
|
||||
maximum_connection_buffer_size,
|
||||
use_legacy_version,
|
||||
);
|
||||
|
||||
let (packet_sender, packet_receiver) = mpsc::unbounded();
|
||||
|
||||
@@ -13,6 +13,7 @@ colored = "2.0"
|
||||
cw3 = "0.13.1"
|
||||
mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" }
|
||||
contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" }
|
||||
coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
|
||||
vesting-contract = { path = "../../../contracts/vesting" }
|
||||
@@ -22,7 +23,7 @@ reqwest = { version = "0.11", features = ["json"] }
|
||||
thiserror = "1"
|
||||
log = "0.4"
|
||||
url = { version = "2.2", features = ["serde"] }
|
||||
tokio = { version = "1.19.1", features = ["sync", "time"] }
|
||||
tokio = { version = "1.21.2", features = ["sync", "time"] }
|
||||
futures = "0.3"
|
||||
|
||||
coconut-interface = { path = "../../coconut-interface" }
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::{validator_api, ValidatorClientError};
|
||||
use mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use mixnet_contract_common::NodeId;
|
||||
use mixnet_contract_common::MixId;
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef};
|
||||
use url::Url;
|
||||
use validator_api_requests::coconut::{
|
||||
@@ -206,7 +206,7 @@ impl<C> Client<C> {
|
||||
// basically handles paging for us
|
||||
pub async fn get_all_nymd_rewarded_set_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<(NodeId, RewardedSetNodeStatus)>, ValidatorClientError>
|
||||
) -> Result<Vec<(MixId, RewardedSetNodeStatus)>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync + Send,
|
||||
{
|
||||
@@ -280,7 +280,7 @@ impl<C> Client<C> {
|
||||
|
||||
pub async fn get_all_nymd_unbonded_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
|
||||
) -> Result<Vec<(MixId, UnbondedMixnode)>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync + Send,
|
||||
{
|
||||
@@ -306,7 +306,7 @@ impl<C> Client<C> {
|
||||
pub async fn get_all_nymd_unbonded_mixnodes_by_owner(
|
||||
&self,
|
||||
owner: &cosmrs::AccountId,
|
||||
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
|
||||
) -> Result<Vec<(MixId, UnbondedMixnode)>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync + Send,
|
||||
{
|
||||
@@ -332,7 +332,7 @@ impl<C> Client<C> {
|
||||
pub async fn get_all_nymd_unbonded_mixnodes_by_identity(
|
||||
&self,
|
||||
identity_key: String,
|
||||
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
|
||||
) -> Result<Vec<(MixId, UnbondedMixnode)>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync + Send,
|
||||
{
|
||||
@@ -384,7 +384,7 @@ impl<C> Client<C> {
|
||||
|
||||
pub async fn get_all_nymd_single_mixnode_delegations(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<Vec<Delegation>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync + Send,
|
||||
@@ -632,7 +632,7 @@ impl ApiClient {
|
||||
|
||||
pub async fn get_mixnode_core_status_count(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
since: Option<i64>,
|
||||
) -> Result<MixnodeCoreStatusResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
@@ -643,14 +643,14 @@ impl ApiClient {
|
||||
|
||||
pub async fn get_mixnode_status(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeStatusResponse, ValidatorClientError> {
|
||||
Ok(self.validator_api.get_mixnode_status(mix_id).await?)
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<RewardEstimationResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.validator_api
|
||||
@@ -660,7 +660,7 @@ impl ApiClient {
|
||||
|
||||
pub async fn get_mixnode_stake_saturation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<StakeSaturationResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.validator_api
|
||||
|
||||
@@ -15,14 +15,12 @@ use cosmrs::rpc::query::Query;
|
||||
use cosmrs::rpc::Error as TendermintRpcError;
|
||||
use cosmrs::rpc::HttpClientUrl;
|
||||
use cosmrs::tx::Msg;
|
||||
use cosmwasm_std::Uint128;
|
||||
use execute::execute;
|
||||
use network_defaults::{ChainDetails, NymNetworkDetails};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::time::SystemTime;
|
||||
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
|
||||
use vesting_contract_common::QueryMsg as VestingQueryMsg;
|
||||
|
||||
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
|
||||
@@ -44,9 +42,10 @@ pub use cosmrs::Coin as CosmosCoin;
|
||||
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
|
||||
pub use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||
use mixnet_contract_common::NodeId;
|
||||
use mixnet_contract_common::MixId;
|
||||
pub use signing_client::Client as SigningNymdClient;
|
||||
pub use traits::{VestingQueryClient, VestingSigningClient};
|
||||
use vesting_contract_common::PledgeCap;
|
||||
|
||||
pub mod coin;
|
||||
pub mod cosmwasm_client;
|
||||
@@ -482,16 +481,6 @@ impl<C> NymdClient<C> {
|
||||
self.client.get_total_supply().await
|
||||
}
|
||||
|
||||
pub async fn vesting_get_locked_pledge_cap(&self) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
let request = VestingQueryMsg::GetLockedPledgeCap {};
|
||||
self.client
|
||||
.query_contract_smart(self.vesting_contract_address(), &request)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn simulate<I, M>(&self, messages: I) -> Result<SimulateResponse, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
@@ -725,7 +714,7 @@ impl<C> NymdClient<C> {
|
||||
#[execute("vesting")]
|
||||
fn _vesting_withdraw_delegator_reward(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
fee: Option<Fee>,
|
||||
) -> (VestingExecuteMsg, Option<Fee>)
|
||||
where
|
||||
@@ -737,12 +726,16 @@ impl<C> NymdClient<C> {
|
||||
#[execute("vesting")]
|
||||
fn _vesting_update_locked_pledge_cap(
|
||||
&self,
|
||||
amount: Uint128,
|
||||
address: String,
|
||||
cap: PledgeCap,
|
||||
fee: Option<Fee>,
|
||||
) -> (VestingExecuteMsg, Option<Fee>)
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
(VestingExecuteMsg::UpdateLockedPledgeCap { amount }, fee)
|
||||
(
|
||||
VestingExecuteMsg::UpdateLockedPledgeCap { address, cap },
|
||||
fee,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,11 @@ use mixnet_contract_common::rewarding::{
|
||||
use mixnet_contract_common::{
|
||||
delegation, ContractBuildInformation, ContractState, ContractStateParams,
|
||||
CurrentIntervalResponse, EpochEventId, GatewayBondResponse, GatewayOwnershipResponse,
|
||||
IdentityKey, IntervalEventId, LayerDistribution, MixOwnershipResponse, MixnodeDetailsResponse,
|
||||
NodeId, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse,
|
||||
PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse, PagedRewardedSetResponse,
|
||||
PendingEpochEventsResponse, PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg,
|
||||
IdentityKey, IntervalEventId, LayerDistribution, MixId, MixOwnershipResponse,
|
||||
MixnodeDetailsResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse,
|
||||
PagedRewardedSetResponse, PendingEpochEventsResponse, PendingIntervalEventsResponse,
|
||||
QueryMsg as MixnetQueryMsg,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -65,7 +66,7 @@ pub trait MixnetQueryClient {
|
||||
|
||||
async fn get_rewarded_set_paged(
|
||||
&self,
|
||||
start_after: Option<NodeId>,
|
||||
start_after: Option<MixId>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedRewardedSetResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetRewardedSet { limit, start_after })
|
||||
@@ -77,7 +78,7 @@ pub trait MixnetQueryClient {
|
||||
async fn get_mixnode_bonds_paged(
|
||||
&self,
|
||||
limit: Option<u32>,
|
||||
start_after: Option<NodeId>,
|
||||
start_after: Option<MixId>,
|
||||
) -> Result<PagedMixnodeBondsResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodeBonds { limit, start_after })
|
||||
.await
|
||||
@@ -86,7 +87,7 @@ pub trait MixnetQueryClient {
|
||||
async fn get_mixnodes_detailed_paged(
|
||||
&self,
|
||||
limit: Option<u32>,
|
||||
start_after: Option<NodeId>,
|
||||
start_after: Option<MixId>,
|
||||
) -> Result<PagedMixnodesDetailsResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodesDetailed { limit, start_after })
|
||||
.await
|
||||
@@ -95,7 +96,7 @@ pub trait MixnetQueryClient {
|
||||
async fn get_unbonded_paged(
|
||||
&self,
|
||||
limit: Option<u32>,
|
||||
start_after: Option<NodeId>,
|
||||
start_after: Option<MixId>,
|
||||
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodes { limit, start_after })
|
||||
.await
|
||||
@@ -105,7 +106,7 @@ pub trait MixnetQueryClient {
|
||||
&self,
|
||||
owner: &AccountId,
|
||||
limit: Option<u32>,
|
||||
start_after: Option<NodeId>,
|
||||
start_after: Option<MixId>,
|
||||
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByOwner {
|
||||
owner: owner.to_string(),
|
||||
@@ -119,7 +120,7 @@ pub trait MixnetQueryClient {
|
||||
&self,
|
||||
identity_key: String,
|
||||
limit: Option<u32>,
|
||||
start_after: Option<NodeId>,
|
||||
start_after: Option<MixId>,
|
||||
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByIdentityKey {
|
||||
identity_key,
|
||||
@@ -141,7 +142,7 @@ pub trait MixnetQueryClient {
|
||||
|
||||
async fn get_mixnode_details(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeDetailsResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDetails { mix_id })
|
||||
.await
|
||||
@@ -149,7 +150,7 @@ pub trait MixnetQueryClient {
|
||||
|
||||
async fn get_mixnode_rewarding_details(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeRewardingDetailsResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeRewardingDetails { mix_id })
|
||||
.await
|
||||
@@ -157,7 +158,7 @@ pub trait MixnetQueryClient {
|
||||
|
||||
async fn get_mixnode_stake_saturation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<StakeSaturationResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetStakeSaturation { mix_id })
|
||||
.await
|
||||
@@ -165,7 +166,7 @@ pub trait MixnetQueryClient {
|
||||
|
||||
async fn get_unbonded_mixnode_information(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<UnbondedMixnodeResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodeInformation { mix_id })
|
||||
.await
|
||||
@@ -212,7 +213,7 @@ pub trait MixnetQueryClient {
|
||||
/// Gets list of all delegations towards particular mixnode on particular page.
|
||||
async fn get_mixnode_delegations_paged(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedMixNodeDelegationsResponse, NymdError> {
|
||||
@@ -228,7 +229,7 @@ pub trait MixnetQueryClient {
|
||||
async fn get_delegator_delegations_paged(
|
||||
&self,
|
||||
delegator: String,
|
||||
start_after: Option<(NodeId, OwnerProxySubKey)>,
|
||||
start_after: Option<(MixId, OwnerProxySubKey)>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedDelegatorDelegationsResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetDelegatorDelegations {
|
||||
@@ -242,7 +243,7 @@ pub trait MixnetQueryClient {
|
||||
/// Checks value of delegation of given client towards particular mixnode.
|
||||
async fn get_delegation_details(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
delegator: &AccountId,
|
||||
proxy: Option<String>,
|
||||
) -> Result<MixNodeDelegationResponse, NymdError> {
|
||||
@@ -277,7 +278,7 @@ pub trait MixnetQueryClient {
|
||||
|
||||
async fn get_pending_mixnode_operator_reward(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<PendingRewardResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetPendingMixNodeOperatorReward { mix_id })
|
||||
.await
|
||||
@@ -286,7 +287,7 @@ pub trait MixnetQueryClient {
|
||||
async fn get_pending_delegator_reward(
|
||||
&self,
|
||||
delegator: &AccountId,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
proxy: Option<String>,
|
||||
) -> Result<PendingRewardResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetPendingDelegatorReward {
|
||||
@@ -300,7 +301,7 @@ pub trait MixnetQueryClient {
|
||||
// given the provided performance, estimate the reward at the end of the current epoch
|
||||
async fn get_estimated_current_epoch_operator_reward(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
estimated_performance: Performance,
|
||||
) -> Result<EstimatedCurrentEpochRewardResponse, NymdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochOperatorReward {
|
||||
@@ -314,7 +315,7 @@ pub trait MixnetQueryClient {
|
||||
async fn get_estimated_current_epoch_delegator_reward(
|
||||
&self,
|
||||
delegator: &AccountId,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
proxy: Option<String>,
|
||||
estimated_performance: Performance,
|
||||
) -> Result<EstimatedCurrentEpochRewardResponse, NymdError> {
|
||||
|
||||
@@ -11,7 +11,7 @@ use cosmrs::AccountId;
|
||||
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
||||
use mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance};
|
||||
use mixnet_contract_common::{
|
||||
ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, MixNode, NodeId,
|
||||
ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, MixId, MixNode,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
@@ -108,7 +108,7 @@ pub trait MixnetSigningClient {
|
||||
|
||||
async fn advance_current_epoch(
|
||||
&self,
|
||||
new_rewarded_set: Vec<NodeId>,
|
||||
new_rewarded_set: Vec<MixId>,
|
||||
expected_active_set_size: u32,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
@@ -324,7 +324,7 @@ pub trait MixnetSigningClient {
|
||||
|
||||
async fn delegate_to_mixnode(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
@@ -339,7 +339,7 @@ pub trait MixnetSigningClient {
|
||||
async fn delegate_to_mixnode_on_behalf(
|
||||
&self,
|
||||
delegate: AccountId,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
@@ -356,7 +356,7 @@ pub trait MixnetSigningClient {
|
||||
|
||||
async fn undelegate_from_mixnode(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
self.execute_mixnet_contract(
|
||||
@@ -370,7 +370,7 @@ pub trait MixnetSigningClient {
|
||||
async fn undelegate_to_mixnode_on_behalf(
|
||||
&self,
|
||||
delegate: AccountId,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
self.execute_mixnet_contract(
|
||||
@@ -388,7 +388,7 @@ pub trait MixnetSigningClient {
|
||||
|
||||
async fn reward_mixnode(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
performance: Performance,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
@@ -425,7 +425,7 @@ pub trait MixnetSigningClient {
|
||||
|
||||
async fn withdraw_delegator_reward(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
self.execute_mixnet_contract(
|
||||
@@ -439,7 +439,7 @@ pub trait MixnetSigningClient {
|
||||
async fn withdraw_delegator_reward_on_behalf(
|
||||
&self,
|
||||
owner: AccountId,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
self.execute_mixnet_contract(
|
||||
|
||||
@@ -6,8 +6,10 @@ pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::NymdClient;
|
||||
use async_trait::async_trait;
|
||||
use contracts_common::ContractBuildInformation;
|
||||
use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp};
|
||||
use mixnet_contract_common::NodeId;
|
||||
use mixnet_contract_common::MixId;
|
||||
use serde::Deserialize;
|
||||
use vesting_contract::vesting::Account;
|
||||
use vesting_contract_common::{
|
||||
messages::QueryMsg as VestingQueryMsg, AllDelegationsResponse, DelegationTimesResponse,
|
||||
@@ -16,6 +18,15 @@ use vesting_contract_common::{
|
||||
|
||||
#[async_trait]
|
||||
pub trait VestingQueryClient {
|
||||
async fn query_vesting_contract<T>(&self, query: VestingQueryMsg) -> Result<T, NymdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>;
|
||||
|
||||
async fn get_vesting_contract_version(&self) -> Result<ContractBuildInformation, NymdError> {
|
||||
self.query_vesting_contract(VestingQueryMsg::GetContractVersion {})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn locked_coins(
|
||||
&self,
|
||||
address: &str,
|
||||
@@ -76,12 +87,12 @@ pub trait VestingQueryClient {
|
||||
async fn get_delegation_timestamps(
|
||||
&self,
|
||||
address: &str,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<DelegationTimesResponse, NymdError>;
|
||||
|
||||
async fn get_all_vesting_delegations_paged(
|
||||
&self,
|
||||
start_after: Option<(u32, NodeId, u64)>,
|
||||
start_after: Option<(u32, MixId, u64)>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<AllDelegationsResponse, NymdError>;
|
||||
|
||||
@@ -107,6 +118,15 @@ pub trait VestingQueryClient {
|
||||
|
||||
#[async_trait]
|
||||
impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
|
||||
async fn query_vesting_contract<T>(&self, query: VestingQueryMsg) -> Result<T, NymdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.client
|
||||
.query_contract_smart(self.vesting_contract_address(), &query)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn locked_coins(
|
||||
&self,
|
||||
vesting_account_address: &str,
|
||||
@@ -269,7 +289,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
|
||||
async fn get_delegation_timestamps(
|
||||
&self,
|
||||
address: &str,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<DelegationTimesResponse, NymdError> {
|
||||
let request = VestingQueryMsg::GetDelegationTimes {
|
||||
address: address.to_string(),
|
||||
@@ -282,7 +302,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
|
||||
|
||||
async fn get_all_vesting_delegations_paged(
|
||||
&self,
|
||||
start_after: Option<(u32, NodeId, u64)>,
|
||||
start_after: Option<(u32, MixId, u64)>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<AllDelegationsResponse, NymdError> {
|
||||
let request = VestingQueryMsg::GetAllDelegations { start_after, limit };
|
||||
|
||||
@@ -7,8 +7,9 @@ use crate::nymd::error::NymdError;
|
||||
use crate::nymd::{Coin, Fee, NymdClient};
|
||||
use async_trait::async_trait;
|
||||
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
||||
use mixnet_contract_common::{Gateway, MixNode, NodeId};
|
||||
use mixnet_contract_common::{Gateway, MixId, MixNode};
|
||||
use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification};
|
||||
use vesting_contract_common::PledgeCap;
|
||||
|
||||
#[async_trait]
|
||||
pub trait VestingSigningClient {
|
||||
@@ -81,21 +82,21 @@ pub trait VestingSigningClient {
|
||||
async fn vesting_track_undelegation(
|
||||
&self,
|
||||
address: &str,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
|
||||
async fn vesting_delegate_to_mixnode(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
|
||||
async fn vesting_undelegate_from_mixnode(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
|
||||
@@ -105,6 +106,7 @@ pub trait VestingSigningClient {
|
||||
staking_address: Option<String>,
|
||||
vesting_spec: Option<VestingSpecification>,
|
||||
amount: Coin,
|
||||
cap: Option<PledgeCap>,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
}
|
||||
@@ -330,7 +332,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
|
||||
async fn vesting_track_undelegation(
|
||||
&self,
|
||||
address: &str,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
@@ -348,7 +350,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
|
||||
|
||||
async fn vesting_delegate_to_mixnode(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
amount: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
@@ -365,7 +367,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
|
||||
|
||||
async fn vesting_undelegate_from_mixnode(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
self.execute_vesting_contract(
|
||||
@@ -382,6 +384,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
|
||||
staking_address: Option<String>,
|
||||
vesting_spec: Option<VestingSpecification>,
|
||||
amount: Coin,
|
||||
cap: Option<PledgeCap>,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
@@ -389,6 +392,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
|
||||
owner_address: owner_address.to_string(),
|
||||
staking_address,
|
||||
vesting_spec,
|
||||
cap,
|
||||
};
|
||||
self.client
|
||||
.execute(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use thiserror::Error;
|
||||
use validator_api_requests::models::RequestError;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ValidatorAPIError {
|
||||
@@ -10,4 +11,7 @@ pub enum ValidatorAPIError {
|
||||
|
||||
#[error("Request failed with error message - {0}")]
|
||||
GenericRequestFailure(String),
|
||||
|
||||
#[error("The validator API has failed to resolve our request. It returned status code {status} and additional error message: {}", error.message())]
|
||||
ApiRequestFailure { status: u16, error: RequestError },
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
use crate::validator_api::error::ValidatorAPIError;
|
||||
use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
|
||||
use mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId};
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
use reqwest::Response;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
use validator_api_requests::coconut::{
|
||||
@@ -12,9 +13,10 @@ use validator_api_requests::coconut::{
|
||||
VerifyCredentialBody, VerifyCredentialResponse,
|
||||
};
|
||||
use validator_api_requests::models::{
|
||||
GatewayCoreStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
|
||||
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
|
||||
InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse,
|
||||
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError,
|
||||
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
|
||||
};
|
||||
|
||||
pub mod error;
|
||||
@@ -47,6 +49,19 @@ impl Client {
|
||||
&self.url
|
||||
}
|
||||
|
||||
async fn send_get_request<K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<Response, ValidatorAPIError>
|
||||
where
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = create_api_url(&self.url, path, params);
|
||||
Ok(self.reqwest_client.get(url).send().await?)
|
||||
}
|
||||
|
||||
async fn query_validator_api<T, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -57,9 +72,36 @@ impl Client {
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = create_api_url(&self.url, path, params);
|
||||
log::trace!("url: {:?}", url.as_str());
|
||||
Ok(self.reqwest_client.get(url).send().await?.json().await?)
|
||||
let res = self.send_get_request(path, params).await?;
|
||||
if res.status().is_success() {
|
||||
Ok(res.json().await?)
|
||||
} else {
|
||||
Err(ValidatorAPIError::GenericRequestFailure(res.text().await?))
|
||||
}
|
||||
}
|
||||
|
||||
// This works for endpoints returning Result<Json<T>, ErrorResponse>
|
||||
async fn query_validator_api_fallible<T, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, ValidatorAPIError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let res = self.send_get_request(path, params).await?;
|
||||
let status = res.status();
|
||||
if res.status().is_success() {
|
||||
Ok(res.json().await?)
|
||||
} else {
|
||||
let request_error: RequestError = res.json().await?;
|
||||
Err(ValidatorAPIError::ApiRequestFailure {
|
||||
status: status.as_u16(),
|
||||
error: request_error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn post_validator_api<B, T, K, V>(
|
||||
@@ -136,6 +178,74 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_report(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeStatusReportResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
routes::REPORT,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_gateway_report(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Result<GatewayStatusReportResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::GATEWAY,
|
||||
identity,
|
||||
routes::REPORT,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_history(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeUptimeHistoryResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
routes::HISTORY,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_gateway_history(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Result<GatewayUptimeHistoryResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::GATEWAY,
|
||||
identity,
|
||||
routes::HISTORY,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_rewarded_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorAPIError> {
|
||||
@@ -184,7 +294,7 @@ impl Client {
|
||||
|
||||
pub async fn get_mixnode_core_status_count(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
since: Option<i64>,
|
||||
) -> Result<MixnodeCoreStatusResponse, ValidatorAPIError> {
|
||||
if let Some(since) = since {
|
||||
@@ -216,7 +326,7 @@ impl Client {
|
||||
|
||||
pub async fn get_mixnode_status(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeStatusResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
@@ -233,9 +343,9 @@ impl Client {
|
||||
|
||||
pub async fn get_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<RewardEstimationResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
self.query_validator_api_fallible(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -250,9 +360,9 @@ impl Client {
|
||||
|
||||
pub async fn get_mixnode_stake_saturation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<StakeSaturationResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
self.query_validator_api_fallible(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -267,9 +377,9 @@ impl Client {
|
||||
|
||||
pub async fn get_mixnode_inclusion_probability(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<InclusionProbabilityResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
self.query_validator_api_fallible(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -284,9 +394,9 @@ impl Client {
|
||||
|
||||
pub async fn get_mixnode_avg_uptime(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Result<UptimeResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
self.query_validator_api_fallible(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
|
||||
@@ -10,7 +10,6 @@ pub const GATEWAYS: &str = "gateways";
|
||||
pub const DETAILED: &str = "detailed";
|
||||
pub const ACTIVE: &str = "active";
|
||||
pub const REWARDED: &str = "rewarded";
|
||||
|
||||
pub const COCONUT_ROUTES: &str = "coconut";
|
||||
pub const BANDWIDTH: &str = "bandwidth";
|
||||
|
||||
@@ -28,6 +27,8 @@ pub const CORE_STATUS_COUNT: &str = "core-status-count";
|
||||
pub const SINCE_ARG: &str = "since";
|
||||
|
||||
pub const STATUS: &str = "status";
|
||||
pub const REPORT: &str = "report";
|
||||
pub const HISTORY: &str = "history";
|
||||
pub const REWARD_ESTIMATION: &str = "reward-estimation";
|
||||
pub const AVG_UPTIME: &str = "avg_uptime";
|
||||
pub const STAKE_SATURATION: &str = "stake-saturation";
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use mixnet_contract_common::{Coin, NodeId};
|
||||
use mixnet_contract_common::{Coin, MixId};
|
||||
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub mix_id: Option<NodeId>,
|
||||
pub mix_id: Option<MixId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: Option<String>,
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::utils::{pretty_cosmwasm_coin, show_error_passthrough};
|
||||
|
||||
use comfy_table::Table;
|
||||
use cosmwasm_std::Addr;
|
||||
use mixnet_contract_common::{Delegation, PendingEpochEvent, PendingEpochEventData};
|
||||
use mixnet_contract_common::{Delegation, PendingEpochEvent, PendingEpochEventKind};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {}
|
||||
@@ -90,8 +90,8 @@ async fn print_delegation_events(
|
||||
]);
|
||||
|
||||
for event in events {
|
||||
match event.event {
|
||||
PendingEpochEventData::Delegate {
|
||||
match event.event.kind {
|
||||
PendingEpochEventKind::Delegate {
|
||||
owner,
|
||||
mix_id,
|
||||
amount,
|
||||
@@ -107,7 +107,7 @@ async fn print_delegation_events(
|
||||
]);
|
||||
}
|
||||
}
|
||||
PendingEpochEventData::Undelegate {
|
||||
PendingEpochEventKind::Undelegate {
|
||||
owner,
|
||||
mix_id,
|
||||
proxy,
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use mixnet_contract_common::NodeId;
|
||||
use mixnet_contract_common::MixId;
|
||||
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub mix_id: Option<NodeId>,
|
||||
pub mix_id: Option<MixId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: Option<String>,
|
||||
|
||||
+2
-2
@@ -4,13 +4,13 @@
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use mixnet_contract_common::NodeId;
|
||||
use mixnet_contract_common::MixId;
|
||||
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub mix_id: Option<NodeId>,
|
||||
pub mix_id: Option<MixId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: Option<String>,
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use mixnet_contract_common::NodeId;
|
||||
use mixnet_contract_common::MixId;
|
||||
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub mix_id: Option<NodeId>,
|
||||
pub mix_id: Option<MixId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: Option<String>,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
use mixnet_contract_common::{Coin, NodeId};
|
||||
use mixnet_contract_common::{Coin, MixId};
|
||||
use validator_client::nymd::traits::MixnetQueryClient;
|
||||
use validator_client::nymd::VestingSigningClient;
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::context::SigningClient;
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub mix_id: Option<NodeId>,
|
||||
pub mix_id: Option<MixId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: Option<String>,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use mixnet_contract_common::NodeId;
|
||||
use mixnet_contract_common::MixId;
|
||||
use validator_client::nymd::traits::MixnetQueryClient;
|
||||
use validator_client::nymd::VestingSigningClient;
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::context::SigningClient;
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub mix_id: Option<NodeId>,
|
||||
pub mix_id: Option<MixId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: Option<String>,
|
||||
|
||||
@@ -5,6 +5,9 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Errors {
|
||||
#[error("account id does not match")]
|
||||
AccountIdError,
|
||||
|
||||
#[error("signature error - {0}")]
|
||||
SignatureError(#[from] k256::ecdsa::signature::Error),
|
||||
|
||||
|
||||
@@ -21,12 +21,22 @@ pub fn secp256k1_verify_with_public_key_json(
|
||||
public_key_as_json: String,
|
||||
signature_as_hex: String,
|
||||
message: String,
|
||||
account_id: String,
|
||||
account_prefix: &str,
|
||||
) -> Result<(), Errors> {
|
||||
let public_key = PublicKey::from_json(&public_key_as_json)?;
|
||||
let verifying_key = VerifyingKey::from_sec1_bytes(&public_key.to_bytes())?;
|
||||
let signature = Signature::from_str(&signature_as_hex)?;
|
||||
let message_as_bytes = message.into_bytes();
|
||||
Ok(verifying_key.verify(&message_as_bytes, &signature)?)
|
||||
match public_key.account_id(account_prefix) {
|
||||
Ok(derived_account_id) => {
|
||||
if derived_account_id.to_string() != account_id {
|
||||
return Err(Errors::AccountIdError);
|
||||
}
|
||||
let verifying_key = VerifyingKey::from_sec1_bytes(&public_key.to_bytes())?;
|
||||
let signature = Signature::from_str(&signature_as_hex)?;
|
||||
let message_as_bytes = message.into_bytes();
|
||||
Ok(verifying_key.verify(&message_as_bytes, &signature)?)
|
||||
}
|
||||
Err(e) => Err(Errors::CosmrsError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -69,9 +79,16 @@ mod test_secp256k1 {
|
||||
let json_public_key = r#"{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A4FdhUMasPmNhRZjtpKlmjNbq7EEUgPxfdI+E3vSajvc"}"#.to_string();
|
||||
let signature_as_hex = "E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28".to_string();
|
||||
let message = "test 1234".to_string();
|
||||
let account_id = "n1lntkptzz8grf2w4yht4szxktzwsucgv4s7vv9g".to_string();
|
||||
let account_prefix = "n";
|
||||
|
||||
let result =
|
||||
secp256k1_verify_with_public_key_json(json_public_key, signature_as_hex, message);
|
||||
let result = secp256k1_verify_with_public_key_json(
|
||||
json_public_key,
|
||||
signature_as_hex,
|
||||
message,
|
||||
account_id,
|
||||
account_prefix,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
@@ -81,9 +98,16 @@ mod test_secp256k1 {
|
||||
let bad_json_public_key = r#"This is not JSON ☠️"#.to_string();
|
||||
let signature_as_hex = "E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28".to_string();
|
||||
let message = "abcdef".to_string();
|
||||
let account_id = "".to_string();
|
||||
let account_prefix = "n";
|
||||
|
||||
let result =
|
||||
secp256k1_verify_with_public_key_json(bad_json_public_key, signature_as_hex, message);
|
||||
let result = secp256k1_verify_with_public_key_json(
|
||||
bad_json_public_key,
|
||||
signature_as_hex,
|
||||
message,
|
||||
account_id,
|
||||
account_prefix,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use validator_client::nymd::AccountId;
|
||||
use validator_client::nymd::VestingSigningClient;
|
||||
use validator_client::nymd::{CosmosCoin, Denom};
|
||||
use vesting_contract_common::messages::VestingSpecification;
|
||||
use vesting_contract_common::PledgeCap;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
@@ -34,6 +35,12 @@ pub struct Args {
|
||||
|
||||
#[clap(long)]
|
||||
pub staking_address: Option<String>,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
help = "Pledge cap as either absolute uNYM value or percentage, floats need to be in the 0.0 to 1.0 range and will be parsed as percentages, integers will be parsed as uNYM"
|
||||
)]
|
||||
pub pledge_cap: Option<PledgeCap>,
|
||||
}
|
||||
|
||||
pub async fn create(args: Args, client: SigningClient, network_details: &NymNetworkDetails) {
|
||||
@@ -55,6 +62,7 @@ pub async fn create(args: Args, client: SigningClient, network_details: &NymNetw
|
||||
args.staking_address,
|
||||
Some(vesting),
|
||||
coin.into(),
|
||||
args.pledge_cap,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -9,3 +9,8 @@ edition = "2021"
|
||||
[dependencies]
|
||||
cosmwasm-std = "1.0.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
thiserror = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.0"
|
||||
|
||||
@@ -11,6 +11,8 @@ use cosmwasm_std::Event;
|
||||
/// * `event`: event to search through.
|
||||
/// * `key`: key associated with the particular attribute
|
||||
pub fn must_find_attribute(event: &Event, key: &str) -> String {
|
||||
// due to how the function is supposed to work, the unwrap is fine in this instance
|
||||
#[allow(clippy::unwrap_used)]
|
||||
may_find_attribute(event, key).unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub mod events;
|
||||
pub mod types;
|
||||
|
||||
|
||||
@@ -1,7 +1,128 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use cosmwasm_std::Decimal;
|
||||
use cosmwasm_std::Uint128;
|
||||
use schemars::JsonSchema;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::ops::Mul;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
|
||||
amount * Uint128::new(1)
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ContractsCommonError {
|
||||
#[error("Provided percent value ({0}) is greater than 100%")]
|
||||
InvalidPercent(Decimal),
|
||||
|
||||
#[error("{source}")]
|
||||
StdErr {
|
||||
#[from]
|
||||
source: cosmwasm_std::StdError,
|
||||
},
|
||||
}
|
||||
|
||||
/// Percent represents a value between 0 and 100%
|
||||
/// (i.e. between 0.0 and 1.0)
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Serialize, Deserialize, JsonSchema,
|
||||
)]
|
||||
pub struct Percent(#[serde(deserialize_with = "de_decimal_percent")] Decimal);
|
||||
|
||||
impl Percent {
|
||||
pub fn new(value: Decimal) -> Result<Self, ContractsCommonError> {
|
||||
if value > Decimal::one() {
|
||||
Err(ContractsCommonError::InvalidPercent(value))
|
||||
} else {
|
||||
Ok(Percent(value))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.0 == Decimal::zero()
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self(Decimal::zero())
|
||||
}
|
||||
|
||||
pub fn hundred() -> Self {
|
||||
Self(Decimal::one())
|
||||
}
|
||||
|
||||
pub fn from_percentage_value(value: u64) -> Result<Self, ContractsCommonError> {
|
||||
Percent::new(Decimal::percent(value))
|
||||
}
|
||||
|
||||
pub fn value(&self) -> Decimal {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn round_to_integer(&self) -> u8 {
|
||||
let hundred = Decimal::from_ratio(100u32, 1u32);
|
||||
// we know the cast from u128 to u8 is a safe one since the internal value must be within 0 - 1 range
|
||||
truncate_decimal(hundred * self.0).u128() as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Percent {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let adjusted = Decimal::from_ratio(100u32, 1u32) * self.0;
|
||||
write!(f, "{}%", adjusted)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Percent {
|
||||
type Err = ContractsCommonError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Percent::new(Decimal::from_str(s)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Decimal> for Percent {
|
||||
type Output = Decimal;
|
||||
|
||||
fn mul(self, rhs: Decimal) -> Self::Output {
|
||||
self.0 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Percent> for Decimal {
|
||||
type Output = Decimal;
|
||||
|
||||
fn mul(self, rhs: Percent) -> Self::Output {
|
||||
rhs * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Uint128> for Percent {
|
||||
type Output = Uint128;
|
||||
|
||||
fn mul(self, rhs: Uint128) -> Self::Output {
|
||||
self.0 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
// implement custom Deserialize because we want to validate Percent has the correct range
|
||||
fn de_decimal_percent<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let v = Decimal::deserialize(deserializer)?;
|
||||
if v > Decimal::one() {
|
||||
Err(D::Error::custom(
|
||||
"provided decimal percent is larger than 100%",
|
||||
))
|
||||
} else {
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: there's no reason this couldn't be used for proper binaries, but in that case
|
||||
// perhaps the struct should get renamed and moved to a "more" common crate
|
||||
@@ -31,3 +152,47 @@ pub struct ContractBuildInformation {
|
||||
/// Provides the rustc version that was used for the build, for example `1.52.0-nightly`.
|
||||
pub rustc_version: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn percent_serde() {
|
||||
let valid_value = Percent::from_percentage_value(80).unwrap();
|
||||
let serialized = serde_json::to_string(&valid_value).unwrap();
|
||||
|
||||
let deserialized: Percent = serde_json::from_str(&serialized).unwrap();
|
||||
assert_eq!(valid_value, deserialized);
|
||||
|
||||
let invalid_values = vec!["\"42\"", "\"1.1\"", "\"1.00000001\"", "\"foomp\"", "\"1a\""];
|
||||
for invalid_value in invalid_values {
|
||||
assert!(serde_json::from_str::<'_, Percent>(invalid_value).is_err())
|
||||
}
|
||||
assert_eq!(
|
||||
serde_json::from_str::<'_, Percent>("\"0.95\"").unwrap(),
|
||||
Percent::from_percentage_value(95).unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn percent_to_absolute_integer() {
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.0001\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 0);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.0099\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 0);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.0199\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 1);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.45123\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 45);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"0.999999999\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 99);
|
||||
|
||||
let p = serde_json::from_str::<'_, Percent>("\"1.00\"").unwrap();
|
||||
assert_eq!(p.round_to_integer(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ schemars = "0.8"
|
||||
thiserror = "1.0"
|
||||
contracts-common = { path = "../contracts-common" }
|
||||
serde_json = "1.0.0"
|
||||
humantime-serde = "1.1.1"
|
||||
|
||||
# TO CHECK WHETHER STILL NEEDED:
|
||||
log = "0.4.14"
|
||||
@@ -28,4 +29,5 @@ time = { version = "0.3.5", features = ["serde", "macros"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
contract-testing = []
|
||||
generate-ts = ['ts-rs']
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
use cosmwasm_std::{Decimal, Uint128};
|
||||
|
||||
pub const TOKEN_SUPPLY: Uint128 = Uint128::new(1_000_000_000_000_000);
|
||||
|
||||
// I'm still not 100% sure how to feel about existence of this file
|
||||
// This is equivalent of representing our display coin with 6 decimal places.
|
||||
|
||||
@@ -4,15 +4,17 @@
|
||||
// due to code generated by JsonSchema
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::{Addr, NodeId};
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
use crate::constants::TOKEN_SUPPLY;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::{Addr, MixId};
|
||||
use cosmwasm_std::{Coin, Decimal, StdResult};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// just use a string representation of those so that we wouldn't need to bother with decoding bytes
|
||||
// and trying to figure out whether they're valid, etc
|
||||
pub type OwnerProxySubKey = String;
|
||||
pub type StorageKey = (NodeId, OwnerProxySubKey);
|
||||
pub type StorageKey = (MixId, OwnerProxySubKey);
|
||||
|
||||
pub fn generate_owner_storage_subkey(address: &Addr, proxy: Option<&Addr>) -> String {
|
||||
if let Some(proxy) = &proxy {
|
||||
@@ -34,13 +36,11 @@ pub struct Delegation {
|
||||
pub owner: Addr,
|
||||
|
||||
/// Id of the MixNode that this delegation was performed against.
|
||||
#[serde(alias = "node_id")]
|
||||
pub mix_id: NodeId,
|
||||
pub mix_id: MixId,
|
||||
|
||||
// Note to UI/UX devs: there's absolutely no point in displaying this value to the users,
|
||||
// it would serve them no purpose. It's only used for calculating rewards
|
||||
/// Value of the "unit delegation" associated with the mixnode at the time of delegation.
|
||||
#[serde(alias = "crr")]
|
||||
pub cumulative_reward_ratio: Decimal,
|
||||
|
||||
/// Original delegation amount. Note that it is never mutated as delegation accumulates rewards.
|
||||
@@ -56,12 +56,17 @@ pub struct Delegation {
|
||||
impl Delegation {
|
||||
pub fn new(
|
||||
owner: Addr,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
cumulative_reward_ratio: Decimal,
|
||||
amount: Coin,
|
||||
height: u64,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
assert!(
|
||||
amount.amount <= TOKEN_SUPPLY,
|
||||
"delegation cannot be larger than the token supply"
|
||||
);
|
||||
|
||||
Delegation {
|
||||
owner,
|
||||
mix_id,
|
||||
@@ -73,7 +78,7 @@ impl Delegation {
|
||||
}
|
||||
|
||||
pub fn generate_storage_key(
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
owner_address: &Addr,
|
||||
proxy: Option<&Addr>,
|
||||
) -> StorageKey {
|
||||
@@ -83,16 +88,14 @@ impl Delegation {
|
||||
// this function might seem a bit redundant, but I'd rather explicitly keep it around in case
|
||||
// some types change in the future
|
||||
pub fn generate_storage_key_with_subkey(
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
owner_proxy_subkey: OwnerProxySubKey,
|
||||
) -> StorageKey {
|
||||
(mix_id, owner_proxy_subkey)
|
||||
}
|
||||
|
||||
pub fn dec_amount(&self) -> Decimal {
|
||||
// the unwrap here is fine as we're guaranteed our base coin amount is going to fit in a Decimal
|
||||
// with 0 decimal places
|
||||
Decimal::from_atomics(self.amount.amount, 0).unwrap()
|
||||
pub fn dec_amount(&self) -> StdResult<Decimal> {
|
||||
self.amount.amount.into_base_decimal()
|
||||
}
|
||||
|
||||
pub fn proxy_storage_key(&self) -> OwnerProxySubKey {
|
||||
@@ -122,13 +125,13 @@ impl PagedMixNodeDelegationsResponse {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedDelegatorDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<(NodeId, OwnerProxySubKey)>,
|
||||
pub start_next_after: Option<(MixId, OwnerProxySubKey)>,
|
||||
}
|
||||
|
||||
impl PagedDelegatorDelegationsResponse {
|
||||
pub fn new(
|
||||
delegations: Vec<Delegation>,
|
||||
start_next_after: Option<(NodeId, OwnerProxySubKey)>,
|
||||
start_next_after: Option<(MixId, OwnerProxySubKey)>,
|
||||
) -> Self {
|
||||
PagedDelegatorDelegationsResponse {
|
||||
delegations,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::NodeId;
|
||||
use crate::MixId;
|
||||
use cosmwasm_std::{Addr, Coin, Decimal};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -13,9 +13,6 @@ pub enum MixnetContractError {
|
||||
source: cosmwasm_std::StdError,
|
||||
},
|
||||
|
||||
#[error("Provided percent value is greater than 100%")]
|
||||
InvalidPercent,
|
||||
|
||||
#[error("Attempted to subtract decimals with overflow ({minuend}.sub({subtrahend}))")]
|
||||
OverflowDecimalSubtraction {
|
||||
minuend: Decimal,
|
||||
@@ -32,7 +29,7 @@ pub enum MixnetContractError {
|
||||
InsufficientDelegation { received: Coin, minimum: Coin },
|
||||
|
||||
#[error("Mixnode ({mix_id}) does not exist")]
|
||||
MixNodeBondNotFound { mix_id: NodeId },
|
||||
MixNodeBondNotFound { mix_id: MixId },
|
||||
|
||||
#[error("{owner} does not seem to own any mixnodes")]
|
||||
NoAssociatedMixNodeBond { owner: Addr },
|
||||
@@ -85,21 +82,21 @@ pub enum MixnetContractError {
|
||||
|
||||
#[error("Mixnode {mix_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")]
|
||||
MixnodeAlreadyRewarded {
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
absolute_epoch_id: u32,
|
||||
},
|
||||
|
||||
#[error("Mixnode {mix_id} hasn't been selected to the rewarding set in this epoch ({absolute_epoch_id})")]
|
||||
MixnodeNotInRewardedSet {
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
absolute_epoch_id: u32,
|
||||
},
|
||||
|
||||
#[error("Mixnode {mix_id} is currently in the process of unbonding")]
|
||||
MixnodeIsUnbonding { mix_id: NodeId },
|
||||
MixnodeIsUnbonding { mix_id: MixId },
|
||||
|
||||
#[error("Mixnode {mix_id} has already unbonded")]
|
||||
MixnodeHasUnbonded { mix_id: NodeId },
|
||||
MixnodeHasUnbonded { mix_id: MixId },
|
||||
|
||||
#[error("The contract has ended up in a state that was deemed impossible: {comment}")]
|
||||
InconsistentState { comment: String },
|
||||
@@ -108,7 +105,7 @@ pub enum MixnetContractError {
|
||||
"Could not find any delegation information associated with mixnode {mix_id} for {address} (proxy: {proxy:?})"
|
||||
)]
|
||||
NoMixnodeDelegationFound {
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
address: String,
|
||||
proxy: Option<String>,
|
||||
},
|
||||
@@ -135,5 +132,5 @@ pub enum MixnetContractError {
|
||||
UnexpectedRewardedSetSize { received: u32, expected: u32 },
|
||||
|
||||
#[error("Mixnode {mix_id} appears multiple times in the provided rewarded set update!")]
|
||||
DuplicateRewardedSetNode { mix_id: NodeId },
|
||||
DuplicateRewardedSetNode { mix_id: MixId },
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
||||
use crate::reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate};
|
||||
use crate::rewarding::RewardDistribution;
|
||||
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer, NodeId};
|
||||
use crate::{BlockHeight, ContractStateParams, IdentityKeyRef, Interval, Layer, MixId};
|
||||
pub use contracts_common::events::*;
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, Event};
|
||||
|
||||
@@ -96,7 +96,7 @@ pub const PROXY_KEY: &str = "proxy";
|
||||
// delegation/undelegation
|
||||
pub const DELEGATOR_KEY: &str = "delegator";
|
||||
pub const DELEGATION_TARGET_KEY: &str = "delegation_target";
|
||||
pub const DELEGATION_HEIGHT_KEY: &str = "delegation_latest_block_height";
|
||||
pub const UNIT_REWARD_KEY: &str = "unit_reward";
|
||||
|
||||
// bonding/unbonding
|
||||
pub const MIX_ID_KEY: &str = "mix_id";
|
||||
@@ -120,58 +120,51 @@ pub const UPDATED_MIXNODE_COST_PARAMS_KEY: &str = "updated_mixnode_cost_params";
|
||||
|
||||
// rewarding
|
||||
pub const INTERVAL_KEY: &str = "interval_details";
|
||||
pub const TOTAL_MIXNODE_REWARD_KEY: &str = "total_node_reward";
|
||||
pub const TOTAL_PLEDGE_KEY: &str = "pledge";
|
||||
pub const TOTAL_DELEGATIONS_KEY: &str = "delegated";
|
||||
pub const OPERATOR_REWARD_KEY: &str = "operator_reward";
|
||||
pub const DELEGATES_REWARD_KEY: &str = "delegates_reward";
|
||||
pub const APPROXIMATE_TIME_LEFT_SECS_KEY: &str = "approximate_time_left_secs";
|
||||
pub const INTERVAL_REWARDING_PARAMS_UPDATE_KEY: &str = "interval_rewarding_params_update";
|
||||
pub const UPDATED_INTERVAL_REWARDING_PARAMS_KEY: &str = "updated_interval_rewarding_params";
|
||||
pub const PRIOR_DELEGATES_KEY: &str = "prior_delegates";
|
||||
pub const PRIOR_UNIT_DELEGATION_KEY: &str = "prior_unit_delegation";
|
||||
pub const PRIOR_UNIT_REWARD_KEY: &str = "prior_unit_reward";
|
||||
|
||||
pub const DISTRIBUTED_DELEGATION_REWARDS_KEY: &str = "distributed_delegation_rewards";
|
||||
pub const FURTHER_DELEGATIONS_TO_REWARD_KEY: &str = "further_delegations";
|
||||
pub const NO_REWARD_REASON_KEY: &str = "no_reward_reason";
|
||||
pub const BOND_NOT_FOUND_VALUE: &str = "bond_not_found";
|
||||
pub const BOND_TOO_FRESH_VALUE: &str = "bond_too_fresh";
|
||||
pub const ZERO_PERFORMANCE_VALUE: &str = "zero_performance";
|
||||
|
||||
// rewarded set update
|
||||
pub const ACTIVE_SET_SIZE_KEY: &str = "active_set_size";
|
||||
pub const REWARDED_SET_SIZE_KEY: &str = "rewarded_set_size";
|
||||
pub const NODES_IN_REWARDED_SET_KEY: &str = "nodes_in_rewarded_set";
|
||||
pub const CURRENT_INTERVAL_ID_KEY: &str = "current_interval";
|
||||
|
||||
pub const NEW_CURRENT_INTERVAL_KEY: &str = "new_current_interval";
|
||||
pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch";
|
||||
pub const BLOCK_HEIGHT_KEY: &str = "block_height";
|
||||
pub const RECONCILIATION_ERROR_EVENT: &str = "reconciliation_error";
|
||||
|
||||
// interval
|
||||
pub const EVENTS_EXECUTED_KEY: &str = "number_of_events_executed";
|
||||
pub const EVENT_CREATION_HEIGHT_KEY: &str = "created_at";
|
||||
pub const REWARDED_SET_NODES_KEY: &str = "rewarded_set_nodes";
|
||||
pub const NEW_EPOCHS_DURATION_SECS_KEY: &str = "new_epoch_durations_secs";
|
||||
pub const NEW_EPOCHS_IN_INTERVAL: &str = "new_epochs_in_interval";
|
||||
|
||||
pub fn new_delegation_event(
|
||||
created_at: BlockHeight,
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
unit_reward: Decimal,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::Delegation)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
|
||||
.add_attribute(UNIT_REWARD_KEY, unit_reward.to_string())
|
||||
}
|
||||
|
||||
pub fn new_delegation_on_unbonded_node_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::Delegation)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
@@ -183,7 +176,7 @@ pub fn new_pending_delegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::PendingDelegation)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
@@ -196,7 +189,7 @@ pub fn new_withdraw_operator_reward_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: Coin,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::WithdrawOperatorReward)
|
||||
.add_attribute(OWNER_KEY, owner.as_str())
|
||||
@@ -209,7 +202,7 @@ pub fn new_withdraw_delegator_reward_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
amount: Coin,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::WithdrawDelegatorReward)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
@@ -218,8 +211,9 @@ pub fn new_withdraw_delegator_reward_event(
|
||||
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_active_set_update_event(new_size: u32) -> Event {
|
||||
pub fn new_active_set_update_event(created_at: BlockHeight, new_size: u32) -> Event {
|
||||
Event::new(MixnetEventType::ActiveSetUpdate)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(ACTIVE_SET_SIZE_KEY, new_size.to_string())
|
||||
}
|
||||
|
||||
@@ -236,10 +230,13 @@ pub fn new_pending_active_set_update_event(
|
||||
}
|
||||
|
||||
pub fn new_rewarding_params_update_event(
|
||||
created_at: BlockHeight,
|
||||
|
||||
update: IntervalRewardingParamsUpdate,
|
||||
updated: IntervalRewardParams,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::IntervalRewardingParamsUpdate)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(
|
||||
INTERVAL_REWARDING_PARAMS_UPDATE_KEY,
|
||||
update.to_inline_json(),
|
||||
@@ -265,8 +262,14 @@ pub fn new_pending_rewarding_params_update_event(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_undelegation_event(delegator: &Addr, proxy: &Option<Addr>, mix_id: NodeId) -> Event {
|
||||
pub fn new_undelegation_event(
|
||||
created_at: BlockHeight,
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::Undelegation)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
@@ -275,7 +278,7 @@ pub fn new_undelegation_event(delegator: &Addr, proxy: &Option<Addr>, mix_id: No
|
||||
pub fn new_pending_undelegation_event(
|
||||
delegator: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::PendingUndelegation)
|
||||
.add_attribute(DELEGATOR_KEY, delegator)
|
||||
@@ -314,7 +317,7 @@ pub fn new_mixnode_bonding_event(
|
||||
proxy: &Option<Addr>,
|
||||
amount: &Coin,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
assigned_layer: Layer,
|
||||
) -> Event {
|
||||
// coin implements Display trait and we use that implementation here
|
||||
@@ -327,15 +330,17 @@ pub fn new_mixnode_bonding_event(
|
||||
.add_attribute(AMOUNT_KEY, amount.to_string())
|
||||
}
|
||||
|
||||
pub fn new_mixnode_unbonding_event(mix_id: NodeId) -> Event {
|
||||
Event::new(MixnetEventType::MixnodeUnbonding).add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::MixnodeUnbonding)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_pending_mixnode_unbonding_event(
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::PendingMixnodeUnbonding)
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
@@ -345,7 +350,7 @@ pub fn new_pending_mixnode_unbonding_event(
|
||||
}
|
||||
|
||||
pub fn new_mixnode_config_update_event(
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
update: &MixNodeConfigUpdate,
|
||||
@@ -358,7 +363,7 @@ pub fn new_mixnode_config_update_event(
|
||||
}
|
||||
|
||||
pub fn new_mixnode_pending_cost_params_update_event(
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
owner: &Addr,
|
||||
proxy: &Option<Addr>,
|
||||
new_costs: &MixNodeCostParams,
|
||||
@@ -371,10 +376,12 @@ pub fn new_mixnode_pending_cost_params_update_event(
|
||||
}
|
||||
|
||||
pub fn new_mixnode_cost_params_update_event(
|
||||
mix_id: NodeId,
|
||||
created_at: BlockHeight,
|
||||
mix_id: MixId,
|
||||
new_costs: &MixNodeCostParams,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::MixnodeCostParamsUpdate)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
|
||||
}
|
||||
@@ -431,7 +438,7 @@ pub fn new_settings_update_event(
|
||||
event
|
||||
}
|
||||
|
||||
pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event {
|
||||
pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::MixnodeRewarding)
|
||||
.add_attribute(
|
||||
INTERVAL_KEY,
|
||||
@@ -441,7 +448,7 @@ pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, mix_id: No
|
||||
.add_attribute(NO_REWARD_REASON_KEY, BOND_NOT_FOUND_VALUE)
|
||||
}
|
||||
|
||||
pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event {
|
||||
pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, mix_id: MixId) -> Event {
|
||||
Event::new(MixnetEventType::MixnodeRewarding)
|
||||
.add_attribute(
|
||||
INTERVAL_KEY,
|
||||
@@ -453,10 +460,10 @@ pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, mix_id:
|
||||
|
||||
pub fn new_mix_rewarding_event(
|
||||
interval: Interval,
|
||||
mix_id: NodeId,
|
||||
mix_id: MixId,
|
||||
reward_distribution: RewardDistribution,
|
||||
prior_delegates: Decimal,
|
||||
prior_unit_delegation: Decimal,
|
||||
prior_unit_reward: Decimal,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::MixnodeRewarding)
|
||||
.add_attribute(
|
||||
@@ -464,7 +471,7 @@ pub fn new_mix_rewarding_event(
|
||||
interval.current_epoch_absolute_id().to_string(),
|
||||
)
|
||||
.add_attribute(PRIOR_DELEGATES_KEY, prior_delegates.to_string())
|
||||
.add_attribute(PRIOR_UNIT_DELEGATION_KEY, prior_unit_delegation.to_string())
|
||||
.add_attribute(PRIOR_UNIT_REWARD_KEY, prior_unit_reward.to_string())
|
||||
.add_attribute(MIX_ID_KEY, mix_id.to_string())
|
||||
.add_attribute(
|
||||
OPERATOR_REWARD_KEY,
|
||||
@@ -500,11 +507,13 @@ pub fn new_reconcile_pending_events() -> Event {
|
||||
}
|
||||
|
||||
pub fn new_interval_config_update_event(
|
||||
created_at: BlockHeight,
|
||||
epochs_in_interval: u32,
|
||||
epoch_duration_secs: u64,
|
||||
updated_rewarding_params: IntervalRewardParams,
|
||||
) -> Event {
|
||||
Event::new(MixnetEventType::IntervalConfigUpdate)
|
||||
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
|
||||
.add_attribute(
|
||||
NEW_EPOCHS_DURATION_SECS_KEY,
|
||||
epoch_duration_secs.to_string(),
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Decimal, StdError, StdResult, Uint128};
|
||||
|
||||
pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
|
||||
let epsilon = epsilon.unwrap_or_else(|| Decimal::from_ratio(1u128, 100_000_000u128));
|
||||
if a > b {
|
||||
assert!(a - b < epsilon, "{} != {}", a, b)
|
||||
} else {
|
||||
assert!(b - a < epsilon, "{} != {}", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_base_decimal(val: impl Into<Uint128>) -> StdResult<Decimal> {
|
||||
val.into_base_decimal()
|
||||
}
|
||||
|
||||
pub trait IntoBaseDecimal {
|
||||
fn into_base_decimal(self) -> StdResult<Decimal>;
|
||||
}
|
||||
|
||||
impl<T> IntoBaseDecimal for T
|
||||
where
|
||||
T: Into<Uint128>,
|
||||
{
|
||||
fn into_base_decimal(self) -> StdResult<Decimal> {
|
||||
let atomics = self.into();
|
||||
Decimal::from_atomics(atomics, 0).map_err(|_| StdError::GenericErr {
|
||||
msg: format!("Decimal range exceeded for {atomics} with 0 decimal places."),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -60,14 +60,25 @@ pub(crate) mod string_rfc3339_offset_date_time {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Interval.ts")
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct Interval {
|
||||
id: IntervalId,
|
||||
epochs_in_interval: u32,
|
||||
|
||||
// TODO add a better TS type generation
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||
#[serde(with = "string_rfc3339_offset_date_time")]
|
||||
// note: the `ts-rs failed to parse this attribute. It will be ignored.` warning emitted during
|
||||
// compilation is fine (I guess). `ts-rs` can't handle `with` serde attribute, but that's okay
|
||||
// since we explicitly specified this field should correspond to typescript's string
|
||||
current_epoch_start: OffsetDateTime,
|
||||
current_epoch_id: EpochId,
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "{ secs: number; nanos: number; }"))]
|
||||
epoch_length: Duration,
|
||||
total_elapsed_epochs: EpochId,
|
||||
}
|
||||
@@ -134,14 +145,17 @@ impl JsonSchema for Interval {
|
||||
impl Interval {
|
||||
/// Initialize epoch in the contract with default values.
|
||||
pub fn init_interval(epochs_in_interval: u32, epoch_length: Duration, env: &Env) -> Self {
|
||||
// if this fails it means the value provided from the chain itself (via cosmwasm) is invalid,
|
||||
// so we really have to panic here as anything beyond that point would be invalid anyway
|
||||
#[allow(clippy::expect_used)]
|
||||
let current_epoch_start =
|
||||
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64)
|
||||
.expect("The timestamp provided via env.block.time is invalid");
|
||||
|
||||
Interval {
|
||||
id: 0,
|
||||
epochs_in_interval,
|
||||
// I really don't see a way for this to fail, unless the blockchain is lying to us
|
||||
current_epoch_start: OffsetDateTime::from_unix_timestamp(
|
||||
env.block.time.seconds() as i64
|
||||
)
|
||||
.expect("Invalid timestamp from env.block.time"),
|
||||
current_epoch_start,
|
||||
current_epoch_id: 0,
|
||||
epoch_length,
|
||||
total_elapsed_epochs: 0,
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
mod constants;
|
||||
pub mod delegation;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod gateway;
|
||||
pub mod helpers;
|
||||
mod interval;
|
||||
pub mod mixnode;
|
||||
mod msg;
|
||||
@@ -33,7 +37,8 @@ pub use mixnode::{
|
||||
};
|
||||
pub use msg::*;
|
||||
pub use pending_events::{
|
||||
PendingEpochEvent, PendingEpochEventData, PendingIntervalEvent, PendingIntervalEventData,
|
||||
PendingEpochEvent, PendingEpochEventData, PendingEpochEventKind, PendingIntervalEvent,
|
||||
PendingIntervalEventData, PendingIntervalEventKind,
|
||||
};
|
||||
pub use reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate, RewardingParams};
|
||||
pub use types::*;
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
// due to code generated by JsonSchema
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use crate::constants::UNIT_DELEGATION_BASE;
|
||||
use crate::constants::{TOKEN_SUPPLY, UNIT_DELEGATION_BASE};
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::reward_params::{NodeRewardParams, RewardingParams};
|
||||
use crate::rewarding::helpers::truncate_reward;
|
||||
use crate::rewarding::RewardDistribution;
|
||||
use crate::{Delegation, EpochId, IdentityKey, NodeId, Percent, SphinxKey};
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
|
||||
use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey};
|
||||
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
@@ -47,8 +48,8 @@ impl MixNodeDetails {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mix_id(&self) -> NodeId {
|
||||
self.bond_information.id
|
||||
pub fn mix_id(&self) -> MixId {
|
||||
self.bond_information.mix_id
|
||||
}
|
||||
|
||||
pub fn is_unbonding(&self) -> bool {
|
||||
@@ -64,7 +65,7 @@ impl MixNodeDetails {
|
||||
self.rewarding_details.pending_operator_reward(pledge)
|
||||
}
|
||||
|
||||
pub fn pending_detailed_operator_reward(&self) -> Decimal {
|
||||
pub fn pending_detailed_operator_reward(&self) -> StdResult<Decimal> {
|
||||
let pledge = self.original_pledge();
|
||||
self.rewarding_details
|
||||
.pending_detailed_operator_reward(pledge)
|
||||
@@ -78,34 +79,27 @@ impl MixNodeDetails {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeRewarding {
|
||||
/// Information provided by the operator that influence the cost function.
|
||||
#[serde(alias = "cp")]
|
||||
pub cost_params: MixNodeCostParams,
|
||||
|
||||
/// Total pledge and compounded reward earned by the node operator.
|
||||
#[serde(alias = "op")]
|
||||
pub operator: Decimal,
|
||||
|
||||
/// Total delegation and compounded reward earned by all node delegators.
|
||||
#[serde(alias = "dg")]
|
||||
pub delegates: Decimal,
|
||||
|
||||
/// Cumulative reward earned by the "unit delegation" since the block 0.
|
||||
#[serde(alias = "tur")]
|
||||
pub total_unit_reward: Decimal,
|
||||
|
||||
/// Value of the theoretical "unit delegation" that has delegated to this mixnode at block 0.
|
||||
#[serde(alias = "ud")]
|
||||
pub unit_delegation: Decimal,
|
||||
|
||||
/// Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt
|
||||
/// to reward it multiple times in the same epoch.
|
||||
#[serde(alias = "le")]
|
||||
pub last_rewarded_epoch: EpochId,
|
||||
|
||||
// technically we don't need that field to determine reward magnitude or anything
|
||||
// but it saves on extra queries to determine if we're removing the final delegation
|
||||
// (so that we could zero the field correctly)
|
||||
#[serde(alias = "uqd")]
|
||||
pub unique_delegations: u32,
|
||||
}
|
||||
|
||||
@@ -114,16 +108,21 @@ impl MixNodeRewarding {
|
||||
cost_params: MixNodeCostParams,
|
||||
initial_pledge: &Coin,
|
||||
current_epoch: EpochId,
|
||||
) -> Self {
|
||||
MixNodeRewarding {
|
||||
) -> Result<Self, MixnetContractError> {
|
||||
assert!(
|
||||
initial_pledge.amount <= TOKEN_SUPPLY,
|
||||
"pledge cannot be larger than the token supply"
|
||||
);
|
||||
|
||||
Ok(MixNodeRewarding {
|
||||
cost_params,
|
||||
operator: Decimal::from_atomics(initial_pledge.amount, 0).unwrap(),
|
||||
operator: initial_pledge.amount.into_base_decimal()?,
|
||||
delegates: Decimal::zero(),
|
||||
total_unit_reward: Decimal::zero(),
|
||||
unit_delegation: UNIT_DELEGATION_BASE,
|
||||
last_rewarded_epoch: current_epoch,
|
||||
unique_delegations: 0,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Determines whether this node is still bonded. This is performed via a simple check,
|
||||
@@ -142,27 +141,30 @@ impl MixNodeRewarding {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> Decimal {
|
||||
let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
|
||||
pub fn pending_detailed_operator_reward(&self, original_pledge: &Coin) -> StdResult<Decimal> {
|
||||
let initial_dec = original_pledge.amount.into_base_decimal()?;
|
||||
if initial_dec > self.operator {
|
||||
panic!(
|
||||
"seems slashing has occurred while it has not been implemented nor accounted for!"
|
||||
)
|
||||
}
|
||||
self.operator - initial_dec
|
||||
Ok(self.operator - initial_dec)
|
||||
}
|
||||
|
||||
pub fn operator_pledge_with_reward(&self, denom: impl Into<String>) -> Coin {
|
||||
truncate_reward(self.operator, denom)
|
||||
}
|
||||
|
||||
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> Coin {
|
||||
let delegator_reward = self.determine_delegation_reward(delegation);
|
||||
truncate_reward(delegator_reward, &delegation.amount.denom)
|
||||
pub fn pending_delegator_reward(&self, delegation: &Delegation) -> StdResult<Coin> {
|
||||
let delegator_reward = self.determine_delegation_reward(delegation)?;
|
||||
Ok(truncate_reward(delegator_reward, &delegation.amount.denom))
|
||||
}
|
||||
|
||||
pub fn withdraw_operator_reward(&mut self, original_pledge: &Coin) -> Coin {
|
||||
let initial_dec = Decimal::from_atomics(original_pledge.amount, 0).unwrap();
|
||||
pub fn withdraw_operator_reward(
|
||||
&mut self,
|
||||
original_pledge: &Coin,
|
||||
) -> Result<Coin, MixnetContractError> {
|
||||
let initial_dec = original_pledge.amount.into_base_decimal()?;
|
||||
if initial_dec > self.operator {
|
||||
panic!(
|
||||
"seems slashing has occurred while it has not been implemented nor accounted for!"
|
||||
@@ -171,14 +173,14 @@ impl MixNodeRewarding {
|
||||
let diff = self.operator - initial_dec;
|
||||
self.operator = initial_dec;
|
||||
|
||||
truncate_reward(diff, &original_pledge.denom)
|
||||
Ok(truncate_reward(diff, &original_pledge.denom))
|
||||
}
|
||||
|
||||
pub fn withdraw_delegator_reward(
|
||||
&mut self,
|
||||
delegation: &mut Delegation,
|
||||
) -> Result<Coin, MixnetContractError> {
|
||||
let reward = self.determine_delegation_reward(delegation);
|
||||
let reward = self.determine_delegation_reward(delegation)?;
|
||||
self.decrease_delegates_decimal(reward)?;
|
||||
|
||||
delegation.cumulative_reward_ratio = self.full_reward_ratio();
|
||||
@@ -308,23 +310,27 @@ impl MixNodeRewarding {
|
||||
self.distribute_rewards(reward_distribution, absolute_epoch_id)
|
||||
}
|
||||
|
||||
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> Decimal {
|
||||
pub fn determine_delegation_reward(&self, delegation: &Delegation) -> StdResult<Decimal> {
|
||||
let starting_ratio = delegation.cumulative_reward_ratio;
|
||||
let ending_ratio = self.full_reward_ratio();
|
||||
let adjust = starting_ratio + UNIT_DELEGATION_BASE;
|
||||
let adjust = starting_ratio + self.unit_delegation;
|
||||
|
||||
(ending_ratio - starting_ratio) * delegation.dec_amount() / adjust
|
||||
Ok((ending_ratio - starting_ratio) * delegation.dec_amount()? / adjust)
|
||||
}
|
||||
|
||||
// this updates `unique_delegations` field
|
||||
pub fn add_base_delegation(&mut self, amount: Uint128) {
|
||||
self.increase_delegates_uint128(amount);
|
||||
pub fn add_base_delegation(&mut self, amount: Uint128) -> Result<(), MixnetContractError> {
|
||||
self.increase_delegates_uint128(amount)?;
|
||||
self.unique_delegations += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn increase_delegates_uint128(&mut self, amount: Uint128) {
|
||||
// the unwrap here is fine as the value is guaranteed to fit under provided constraints
|
||||
self.delegates += Decimal::from_atomics(amount, 0).unwrap()
|
||||
pub fn increase_delegates_uint128(
|
||||
&mut self,
|
||||
amount: Uint128,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
self.delegates += amount.into_base_decimal()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// this updates `unique_delegations` field
|
||||
@@ -342,7 +348,7 @@ impl MixNodeRewarding {
|
||||
&mut self,
|
||||
amount: Uint128,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let amount_dec = Decimal::from_atomics(amount, 0).unwrap();
|
||||
let amount_dec = amount.into_base_decimal()?;
|
||||
self.decrease_delegates_decimal(amount_dec)
|
||||
}
|
||||
|
||||
@@ -375,8 +381,8 @@ impl MixNodeRewarding {
|
||||
}
|
||||
|
||||
pub fn undelegate(&mut self, delegation: &Delegation) -> Result<Coin, MixnetContractError> {
|
||||
let reward = self.determine_delegation_reward(delegation);
|
||||
let full_amount = reward + delegation.dec_amount();
|
||||
let reward = self.determine_delegation_reward(delegation)?;
|
||||
let full_amount = reward + delegation.dec_amount()?;
|
||||
self.remove_delegation_decimal(full_amount)?;
|
||||
Ok(truncate_reward(full_amount, &delegation.amount.denom))
|
||||
}
|
||||
@@ -428,7 +434,7 @@ impl MixNodeRewarding {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeBond {
|
||||
/// Unique id assigned to the bonded mixnode.
|
||||
pub id: NodeId,
|
||||
pub mix_id: MixId,
|
||||
|
||||
/// Address of the owner of this mixnode.
|
||||
pub owner: Addr,
|
||||
@@ -456,7 +462,7 @@ pub struct MixNodeBond {
|
||||
|
||||
impl MixNodeBond {
|
||||
pub fn new(
|
||||
id: NodeId,
|
||||
mix_id: MixId,
|
||||
owner: Addr,
|
||||
original_pledge: Coin,
|
||||
layer: Layer,
|
||||
@@ -465,7 +471,7 @@ impl MixNodeBond {
|
||||
bonding_height: u64,
|
||||
) -> Self {
|
||||
MixNodeBond {
|
||||
id,
|
||||
mix_id,
|
||||
owner,
|
||||
original_pledge,
|
||||
layer,
|
||||
@@ -580,6 +586,7 @@ pub struct UnbondedMixnode {
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
|
||||
pub proxy: Option<Addr>,
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "number"))]
|
||||
pub unbonding_height: u64,
|
||||
}
|
||||
|
||||
@@ -607,11 +614,11 @@ impl MixNodeConfigUpdate {
|
||||
pub struct PagedMixnodeBondsResponse {
|
||||
pub nodes: Vec<MixNodeBond>,
|
||||
pub per_page: usize,
|
||||
pub start_next_after: Option<NodeId>,
|
||||
pub start_next_after: Option<MixId>,
|
||||
}
|
||||
|
||||
impl PagedMixnodeBondsResponse {
|
||||
pub fn new(nodes: Vec<MixNodeBond>, per_page: usize, start_next_after: Option<NodeId>) -> Self {
|
||||
pub fn new(nodes: Vec<MixNodeBond>, per_page: usize, start_next_after: Option<MixId>) -> Self {
|
||||
PagedMixnodeBondsResponse {
|
||||
nodes,
|
||||
per_page,
|
||||
@@ -624,14 +631,14 @@ impl PagedMixnodeBondsResponse {
|
||||
pub struct PagedMixnodesDetailsResponse {
|
||||
pub nodes: Vec<MixNodeDetails>,
|
||||
pub per_page: usize,
|
||||
pub start_next_after: Option<NodeId>,
|
||||
pub start_next_after: Option<MixId>,
|
||||
}
|
||||
|
||||
impl PagedMixnodesDetailsResponse {
|
||||
pub fn new(
|
||||
nodes: Vec<MixNodeDetails>,
|
||||
per_page: usize,
|
||||
start_next_after: Option<NodeId>,
|
||||
start_next_after: Option<MixId>,
|
||||
) -> Self {
|
||||
PagedMixnodesDetailsResponse {
|
||||
nodes,
|
||||
@@ -643,16 +650,16 @@ impl PagedMixnodesDetailsResponse {
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedUnbondedMixnodesResponse {
|
||||
pub nodes: Vec<(NodeId, UnbondedMixnode)>,
|
||||
pub nodes: Vec<(MixId, UnbondedMixnode)>,
|
||||
pub per_page: usize,
|
||||
pub start_next_after: Option<NodeId>,
|
||||
pub start_next_after: Option<MixId>,
|
||||
}
|
||||
|
||||
impl PagedUnbondedMixnodesResponse {
|
||||
pub fn new(
|
||||
nodes: Vec<(NodeId, UnbondedMixnode)>,
|
||||
nodes: Vec<(MixId, UnbondedMixnode)>,
|
||||
per_page: usize,
|
||||
start_next_after: Option<NodeId>,
|
||||
start_next_after: Option<MixId>,
|
||||
) -> Self {
|
||||
PagedUnbondedMixnodesResponse {
|
||||
nodes,
|
||||
@@ -670,25 +677,25 @@ pub struct MixOwnershipResponse {
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct MixnodeDetailsResponse {
|
||||
pub mix_id: NodeId,
|
||||
pub mix_id: MixId,
|
||||
pub mixnode_details: Option<MixNodeDetails>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct MixnodeRewardingDetailsResponse {
|
||||
pub mix_id: NodeId,
|
||||
pub mix_id: MixId,
|
||||
pub rewarding_details: Option<MixNodeRewarding>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct UnbondedMixnodeResponse {
|
||||
pub mix_id: NodeId,
|
||||
pub mix_id: MixId,
|
||||
pub unbonded_info: Option<UnbondedMixnode>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct StakeSaturationResponse {
|
||||
pub mix_id: NodeId,
|
||||
pub mix_id: MixId,
|
||||
pub current_saturation: Option<Decimal>,
|
||||
pub uncapped_saturation: Option<Decimal>,
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user